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

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.ui.internal.views.AbstractPresentationView.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.Point;
import java.awt.event.InputEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ProgressIndicator;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.operations.UndoRedoActionGroup;
import org.piccolo2d.PCamera;
import org.piccolo2d.PNode;
import org.piccolo2d.event.PDragSequenceEventHandler;
import org.piccolo2d.event.PInputEvent;
import org.piccolo2d.event.PInputEventFilter;
import org.piccolo2d.event.PInputEventListener;
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.PBounds;
import org.piccolo2d.util.PDimension;
import org.piccolo2d.util.PPaintContext;

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.core.BagChange;
import com.bdaum.zoom.core.CatalogAdapter;
import com.bdaum.zoom.core.CatalogListener;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.QueryField;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.operations.UpdateTextIndexOperation;
import com.bdaum.zoom.css.internal.IExtendedColorModel2;
import com.bdaum.zoom.job.OperationJob;
import com.bdaum.zoom.ui.AssetSelection;
import com.bdaum.zoom.ui.IZoomCommandIds;
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.UiConstants;
import com.bdaum.zoom.ui.internal.UiUtilities;
import com.bdaum.zoom.ui.internal.actions.ZoomActionFactory;
import com.bdaum.zoom.ui.internal.hover.HoverInfo;
import com.bdaum.zoom.ui.internal.hover.IGalleryHover;
import com.bdaum.zoom.ui.internal.hover.IHoverInfo;
import com.bdaum.zoom.ui.internal.widgets.AbstractHandle;
import com.bdaum.zoom.ui.internal.widgets.GalleryPanEventHandler;
import com.bdaum.zoom.ui.internal.widgets.GalleryZoomEventHandler;
import com.bdaum.zoom.ui.internal.widgets.GreekedPSWTText;
import com.bdaum.zoom.ui.internal.widgets.InertiaMouseWheelListener;
import com.bdaum.zoom.ui.internal.widgets.PContainer;
import com.bdaum.zoom.ui.internal.widgets.PSWTAssetThumbnail;
import com.bdaum.zoom.ui.internal.widgets.PSWTButton;
import com.bdaum.zoom.ui.internal.widgets.PSWTSectionBreak;
import com.bdaum.zoom.ui.internal.widgets.PTextHandler;
import com.bdaum.zoom.ui.internal.widgets.TextEventHandler;
import com.bdaum.zoom.ui.internal.widgets.TextField;
import com.bdaum.zoom.ui.internal.widgets.ZPSWTCanvas;
import com.bdaum.zoom.ui.preferences.PreferenceConstants;

@SuppressWarnings("restriction")
public abstract class AbstractPresentationView extends BasicView
        implements IExtendedColorModel2, IPresentationHandler, UiConstants, IOperationHistoryListener {

    private static final int PROGRESS_THICKNESS = 5;
    private static final int FORCE_PAN_MASK = InputEvent.SHIFT_MASK | InputEvent.ALT_MASK | InputEvent.CTRL_MASK
            | InputEvent.BUTTON3_MASK;

    protected final class PPresentationPanel extends PSWTPath {

        private static final long serialVersionUID = -4203919566444641490L;

        public PPresentationPanel(float x, float y, float w, float h, Color color, Color strokeColor,
                PInputEventListener inputEventHandler) {
            setPathToRectangle(x, y, w, h);
            setPaint(color);
            if (strokeColor != null)
                setStrokeColor(strokeColor);
            setPickable(true);
            if (inputEventHandler != null)
                addInputEventListener(inputEventHandler);
        }
    }

    protected abstract class PPresentationItem extends PNode implements PTextHandler, IPresentationItem {

        private static final long serialVersionUID = 6878323979541287268L;
        protected PSWTImage pImage;
        protected TextField caption;
        private boolean selected;
        private PSWTPath frame;

        @Override
        public void setBackgroundColor(Color color) {
            // do nothing
        }

        public void setSequenceNo(int i) {
            // do nothing
        }

        @Override
        public void updateColors(Color selectedPaint) {
            // do nothing
        }

        public boolean isSelected() {
            return selected;
        }

        public void setSelected(boolean selected) {
            if (this.selected != selected) {
                if (frame != null)
                    frame.setVisible(selected);
                this.selected = selected;
            }
        }

        protected PSWTPath createSelectionFrame(PBounds bounds) {
            frame = PSWTPath.createRectangle((float) bounds.getX(), (float) bounds.getY(),
                    (float) bounds.getWidth(), (float) bounds.getHeight());
            frame.setStrokeColor(new Color(255, 64, 0));
            frame.setPaint(null);
            frame.setVisible(false);
            frame.setPickable(false);
            return frame;
        }
    }

    protected static abstract class AbstractHandleDragSequenceEventHandler extends PDragSequenceEventHandler {
        protected Point2D oldOffset;
        private final PNode node;
        private final PNode presentationObject;
        private Point2D oldPos;
        protected final boolean relative;
        private final boolean restricted;

        public AbstractHandleDragSequenceEventHandler(PNode node, boolean relative, boolean restricted,
                PNode presentationObject) {
            this.node = node;
            this.relative = relative;
            this.restricted = restricted;
            this.presentationObject = presentationObject;
        }

        @Override
        public boolean acceptsEvent(PInputEvent event, int type) {
            PNode pickedNode2 = event.getPickedNode();
            if (pickedNode2 instanceof PSWTText)
                return false;
            return super.acceptsEvent(event, type);
        }

        @Override
        protected void startDrag(PInputEvent event) {
            super.startDrag(event);
            moveToFront(presentationObject);
            oldOffset = (relative ? node : presentationObject).getOffset();
            oldPos = event.getCanvasPosition();
        }

        protected void moveToFront(PNode object) {
            presentationObject.raiseToTop();
            object.raiseToTop();
        }

        @Override
        protected void drag(PInputEvent event) {
            super.drag(event);
            PDimension aDelta = event.getDeltaRelativeTo(presentationObject);
            if (aDelta.getWidth() != 0 || aDelta.getHeight() != 0)
                (relative ? node : presentationObject).offset(aDelta.getWidth(), aDelta.getHeight());
        }

        @Override
        protected void endDrag(PInputEvent event) {
            super.endDrag(event);
            Point2D pos = event.getCanvasPosition();
            org.eclipse.swt.graphics.Point dragTolerance = getDragTolerance();
            boolean doDrop = Math.abs(pos.getX() - oldPos.getX()) > dragTolerance.x
                    || Math.abs(pos.getY() - oldPos.getY()) > dragTolerance.y;
            if (relative && restricted) {
                PBounds globalBounds = node.getGlobalBounds();
                ListIterator<?> childrenIterator = presentationObject.getChildrenIterator();
                while (childrenIterator.hasNext()) {
                    PNode child = (PNode) childrenIterator.next();
                    if (child != node && globalBounds.intersects(child.getGlobalBounds())) {
                        doDrop = false;
                        break;
                    }
                }
            }
            if (doDrop)
                drop(event, node);
            else
                (relative ? node : presentationObject).setOffset(oldOffset);
        }

        protected abstract void drop(PInputEvent event, PNode nod);

        public abstract org.eclipse.swt.graphics.Point getDragTolerance();
    }

    public class GalleryHover implements IGalleryHover {

        public IHoverInfo getHoverInfo(IHoverSubject viewer, MouseEvent event) {
            Object object = viewer.findObject(event);
            if (object instanceof AbstractHandle) {
                AbstractHandle handle = (AbstractHandle) object;
                if (handle.getVisible())
                    return new HoverInfo(object, ((AbstractHandle) object).getTooltip());
                object = handle.getParent();
            }
            if (object instanceof PSWTButton)
                return new HoverInfo(object, ((PSWTButton) object).getTooltip());
            if (object instanceof PSWTSectionBreak)
                return new HoverInfo(object, createHoverTitle((PSWTSectionBreak) object),
                        createHoverText((PSWTSectionBreak) object));
            if (object instanceof PSWTAssetThumbnail && ((PSWTAssetThumbnail) object).getAsset() != null)
                return new HoverInfo(object, createHoverTitle((PSWTAssetThumbnail) object),
                        createHoverText((PSWTAssetThumbnail) object));
            if (object instanceof GreekedPSWTText) {
                GreekedPSWTText field = (GreekedPSWTText) object;
                if (field.isGreek())
                    return new HoverInfo(object, field.getText());
            }
            return null;
        }
    }

    private static final double PAN_SENSITIVITY = 0.3d;
    private static final double VACCEL = 2.5d;
    private static final double HACCEL = 5d;
    private static final java.awt.Rectangle RECT1 = new java.awt.Rectangle(0, 0, 1, 1);

    protected PSWTCanvas canvas;
    protected java.awt.Rectangle surfaceBounds;
    protected PSWTPath surface;
    private PNode pickedNode;
    protected InertiaMouseWheelListener wheelListener;
    protected TextEventHandler textEventHandler;
    protected List<Image> images = new ArrayList<Image>(200);
    protected int lastMouseX;
    protected boolean mouseDown;
    protected int mouseButton;
    private String currentPresentation;
    private int currentSystemCursor = SWT.CURSOR_ARROW;
    private String currentCustomCursor;
    protected Color offlineColor, remoteColor, selectedRemoteColor, foregroundColor, selectionForegroundColor,
            selectionBackgroundColor, backgroundColor, titleForegroundColor, titleBackgroundColor;
    protected IAction showInFolderAction, showInTimeLineAction, synchronizeAction, gotoExhibitAction,
            propertiesAction, showInMapAction, showFullscreenAction, editAction, editWithAction, rotateLeftAction,
            rotateRightAction, voiceNoteAction, deleteAction, cutAction, pasteAction, exhibitPropertiesAction,
            gotoLastSelectedAction;
    protected Point2D positionRelativeToCamera;
    protected boolean synchronize;
    private IOperationHistory operationHistory;
    private GalleryPanEventHandler panEventHandler;
    private GalleryZoomEventHandler zoomEventHandler;
    protected double previousMagnification = 1d;
    protected AffineTransform oldTransform;
    private Cursor cue;
    private int mouseX, mouseY;
    protected Point2D positionX, currentMousePosition;
    protected ProgressIndicator progressBar;
    private MenuManager menuMgr;
    protected PPresentationItem lastSelection, recentSelection;
    protected List<IIdentifiableObject> clipboard = new ArrayList<>();

    protected static TextField createTextLine(PNode parent, String text, int greekThreshold, int textWidth,
            int indent, double ypos, Color penColor, Color bgColor, String fontFamily, int fontStyle, int fontSize,
            int spellingOptions, int style) {
        return createTextLine(parent, text, greekThreshold, textWidth, indent, ypos, penColor, bgColor,
                new Font(fontFamily, fontStyle, fontSize), spellingOptions, style);
    }

    protected static TextField createTextLine(PNode parent, String text, int greekThreshold, int textWidth,
            int indent, double ypos, Color penColor, Color bgColor, Font font, int spellingOptions, int style) {
        TextField field = new TextField(text, textWidth, font, penColor, bgColor, true, style);
        field.setSpellingOptions(10, spellingOptions);
        field.setOffset(indent, ypos);
        field.setPickable(true);
        field.setGreekThreshold(greekThreshold);
        parent.addChild(field);
        return field;
    }

    protected abstract String createHoverTitle(PNode object);

    protected abstract String createHoverText(PNode thumbnail);

    @Override
    public void createPartControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.verticalSpacing = 0;
        layout.marginHeight = 0;
        layout.marginLeft = 0;
        composite.setLayout(layout);
        canvas = new ZPSWTCanvas(composite, SWT.NONE);
        canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        canvas.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
        setCanvasCursor(null, SWT.CURSOR_APPSTARTING);
        surfaceBounds = computeSurfaceSize(canvas.getDisplay().getPrimaryMonitor().getBounds());
        surface = PSWTPath.createRectangle(surfaceBounds.x, surfaceBounds.y, surfaceBounds.width,
                surfaceBounds.height);
        canvas.getLayer().addChild(surface);
        canvas.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDown(MouseEvent e) {
                mouseDown = true;
                mouseButton = e.button;
                mouseX = lastMouseX = e.x;
                mouseY = e.y;
                setCursorForObject(e, CURSOR_GRABBING, CURSOR_MPLUS, CURSOR_MPLUS);
            }

            @Override
            public void mouseUp(MouseEvent e) {
                mouseX = e.x;
                mouseY = e.y;
                mouseDown = false;
                setCursorForObject(e, CURSOR_OPEN_HAND, CURSOR_OPEN_HAND, CURSOR_OPEN_HAND);
            }
        });
        canvas.addMouseMoveListener(new MouseMoveListener() {
            public void mouseMove(MouseEvent e) {
                mouseX = e.x;
                mouseY = e.y;
                if (mouseDown) {
                    e.button = mouseButton;
                    setCursorForObject(e, mouseButton > 1 ? CURSOR_OPEN_HAND : CURSOR_GRABBING, CURSOR_MPLUS,
                            CURSOR_MMINUS);
                } else
                    setCursorForObject(e, CURSOR_OPEN_HAND, CURSOR_OPEN_HAND, CURSOR_OPEN_HAND);
            }
        });
        progressBar = new ProgressIndicator(composite, SWT.BORDER);
        GridData data = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
        data.heightHint = PROGRESS_THICKNESS;
        progressBar.setLayoutData(data);
        addKeyListener();
        getSite().getWorkbenchWindow().getWorkbench().getOperationSupport().getOperationHistory()
                .addOperationHistoryListener(new IOperationHistoryListener() {
                    public void historyNotification(OperationHistoryEvent event) {
                        refresh();
                    }
                });
    }

    @Override
    protected void makeActions() {
        super.makeActions();
        showFullscreenAction = addAction(ZoomActionFactory.VIEWIMAGE.create(null, this));
        editAction = addAction(ZoomActionFactory.EDIT.create(null, this));
        editWithAction = addAction(ZoomActionFactory.EDITWITH.create(null, this));
        rotateLeftAction = addAction(ZoomActionFactory.ROTATELEFT.create(null, this));
        rotateRightAction = addAction(ZoomActionFactory.ROTATERIGHT.create(null, this));
        showInFolderAction = addAction(ZoomActionFactory.SHOWINFOLDER.create(null, this));
        showInTimeLineAction = addAction(ZoomActionFactory.SHOWINTIMELINE.create(null, this));
        showInMapAction = addAction(ZoomActionFactory.SHOWINMAP.create(null, this));
        voiceNoteAction = addAction(ZoomActionFactory.PLAYVOICENOTE.create(null, this));
        synchronizeAction = new Action(Messages.getString("AbstractPresentationView.synchronize"), //$NON-NLS-1$
                IAction.AS_CHECK_BOX) {
            @Override
            public void run() {
                synchronize = !synchronize;
                synchronize();
            }
        };
        synchronizeAction.setToolTipText(Messages.getString("AbstractPresentationView.synchronize_tooltip")); //$NON-NLS-1$
        synchronizeAction.setImageDescriptor(Icons.sync.getDescriptor());
        deleteAction = new Action(Messages.getString("AbstractPresentationView.delete_item"), //$NON-NLS-1$
                Icons.delete.getDescriptor()) {
            @Override
            public void run() {
                PPresentationItem item = (PPresentationItem) getAdapter(IPresentationItem.class);
                if (item != null)
                    deletePresentationItem(item, false, true);
            }
        };
        deleteAction.setToolTipText(Messages.getString("AbstractPresentationView.delete_tooltip")); //$NON-NLS-1$
        cutAction = new Action(Messages.getString("AbstractPresentationView.cut"), Icons.cut.getDescriptor()) { //$NON-NLS-1$
            @Override
            public void run() {
                IPresentationItem item = (IPresentationItem) getAdapter(IPresentationItem.class);
                if (item != null)
                    deletePresentationItem(item, true, true);
            }
        };
        cutAction.setToolTipText(Messages.getString("AbstractPresentationView.cut_tooltip")); //$NON-NLS-1$
        pasteAction = new Action(Messages.getString("AbstractPresentationView.paste"), //$NON-NLS-1$
                Icons.paste.getDescriptor()) {

            @Override
            public void runWithEvent(Event event) {
                if (event.type != SWT.Selection)
                    positionX = currentMousePosition;
                super.runWithEvent(event);
            }

            @Override
            public void run() {
                pastePresentationItems();
            }
        };
        pasteAction.setToolTipText(Messages.getString("AbstractPresentationView.paste_tooltip")); //$NON-NLS-1$

        gotoLastSelectedAction = new Action(Messages.getString("AbstractPresentationView.goto_sel")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (recentSelection != null)
                    revealItem(recentSelection);
            }
        };
        gotoLastSelectedAction.setToolTipText(Messages.getString("AbstractPresentationView.goto_sel_tooltip")); //$NON-NLS-1$
    }

    protected void revealItem(PPresentationItem item) {
        if (item != null)
            canvas.getCamera().setViewBounds(item.getGlobalFullBounds());
    }

    protected abstract void pastePresentationItems();

    protected abstract void deletePresentationItem(IPresentationItem item, boolean cut, boolean multiple);

    protected void synchronize() {
        if (synchronize && pickedNode != null) {
            PNode node = pickedNode.getParent();
            while (node != null) {
                if (node instanceof IPresentationItem) {
                    getNavigationHistory().postSelection((AssetSelection) getAdapter(AssetSelection.class));
                    break;
                }
                node = node.getParent();
            }
        }
    }

    private void setCursorForObject(MouseEvent e, String surface, String altLeft, String altRight) {
        boolean zooming = false;
        boolean panning = false;
        int zoomKey = Platform.getPreferencesService().getInt(UiActivator.PLUGIN_ID, PreferenceConstants.ZOOMKEY,
                PreferenceConstants.ZOOMALT, null);
        if (e.button >= 2) {
            zooming = zoomKey == PreferenceConstants.ZOOMRIGHT;
            panning = !zooming;
        } else if (e.button == 1) {
            switch (zoomKey) {
            case PreferenceConstants.ZOOMALT:
                zooming = ((e.stateMask & SWT.ALT) != 0);
                panning = ((e.stateMask & SWT.SHIFT) != 0);
                break;
            case PreferenceConstants.ZOOMSHIFT:
                zooming = ((e.stateMask & SWT.SHIFT) != 0);
                panning = ((e.stateMask & SWT.ALT) != 0);
                break;
            case PreferenceConstants.ZOOMRIGHT:
            case PreferenceConstants.NOZOOM:
                panning = ((e.stateMask & (SWT.ALT | SWT.SHIFT)) != 0);
                break;
            }
        }
        if (zooming) {
            if (Math.abs(lastMouseX - e.x) > 3)
                unhookContextMenu();
            setCanvasCursor(e.x <= lastMouseX ? altRight : altLeft, -1);
            lastMouseX = e.x;
            return;
        }
        if (panning) {
            setCanvasCursor(surface, -1);
            return;
        }
        setObjectCursor(findObject(e), e.button, surface);
        hookContextMenu();
    }

    private void setObjectCursor(PNode obj, int button, String surface) {
        if (obj instanceof AbstractHandle) {
            AbstractHandle handle = (AbstractHandle) obj;
            if (handle.getVisible()) {
                setCanvasCursor(null, handle.getCursorType());
                return;
            }
            obj = handle.getParent();
        }
        if (obj instanceof PSWTButton)
            setCanvasCursor(null, SWT.CURSOR_HAND);
        else if (obj instanceof PSWTText)
            setCanvasCursor(null, obj.getParent() instanceof TextField ? SWT.CURSOR_IBEAM : SWT.CURSOR_ARROW);
        else if (obj instanceof PSWTAssetThumbnail || obj instanceof PPresentationPanel)
            setCanvasCursor(null, SWT.CURSOR_SIZEALL);
        else
            setCanvasCursor(surface, -1);
    }

    protected void setCanvasCursor(String customCursor, int systemCursor) {
        if (cue != null) {
            canvas.setCursor(cue);
            currentSystemCursor = -1;
            currentCustomCursor = null;
            return;
        }
        if (customCursor == null) {
            if (currentSystemCursor != systemCursor || currentCustomCursor != null) {
                canvas.setCursor(canvas.getDisplay().getSystemCursor(systemCursor));
                currentSystemCursor = systemCursor;
                currentCustomCursor = null;
            }
        } else if (currentCustomCursor != customCursor || currentSystemCursor >= 0) {
            canvas.setCursor(UiActivator.getDefault().getCursor(canvas.getDisplay(), customCursor));
            currentSystemCursor = -1;
            currentCustomCursor = customCursor;
        }
    }

    @Override
    public PNode findObject(MouseEvent event) {
        return findObject(event.x, event.y);
    }

    private PNode findObject(int x, int y) {
        Point2D viewDimension = new Point(x, y);
        canvas.getCamera().localToView(viewDimension);
        ArrayList<PNode> results = new ArrayList<PNode>();
        RECT1.x = (int) viewDimension.getX();
        RECT1.y = (int) viewDimension.getY();
        surface.findIntersectingNodes(RECT1, results);
        for (PNode object : results)
            if (object instanceof PSWTText || object instanceof PSWTButton || object instanceof AbstractHandle)
                return object;
        for (PNode object : results)
            if (object instanceof PSWTAssetThumbnail || object instanceof PSWTSectionBreak)
                return object;
        for (PNode object : results)
            if (object instanceof PPresentationPanel)
                return object;
        for (PNode object : results)
            if (object instanceof PContainer)
                return object;
        return null;
    }

    @Override
    public IGalleryHover getGalleryHover(MouseEvent event) {
        return new GalleryHover();
    }

    @Override
    public void themeChanged() {
        setColor(canvas);
    }

    protected java.awt.Rectangle computeSurfaceSize(Rectangle bounds) {
        return new java.awt.Rectangle(-bounds.width * 10, -bounds.height * 10, bounds.width * 20,
                bounds.height * 20);
    }

    protected void addCatalogListener(CatalogListener l) {
        Core.getCore().addCatalogListener(l);
    }

    @Override
    public boolean assetsChanged() {
        return false;
    }

    @Override
    public boolean collectionChanged() {
        return false;
    }

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

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

    public void setTitleColor(org.eclipse.swt.graphics.Color c) {
        titleForegroundColor = UiUtilities.getAwtForeground(getControl(), c);
    }

    public void setSelectedOfflineColor(org.eclipse.swt.graphics.Color selectedOfflineColor) {
        // do nothing
    }

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

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

    public void setForegroundColor(org.eclipse.swt.graphics.Color c) {
        foregroundColor = UiUtilities.getAwtForeground(getControl(), c);
    }

    public void setSelectionForegroundColor(org.eclipse.swt.graphics.Color c) {
        selectionForegroundColor = UiUtilities.getAwtForeground(getControl(), c);
    }

    public void setSelectionBackgroundColor(org.eclipse.swt.graphics.Color c) {
        selectionBackgroundColor = UiUtilities.getAwtBackground(getControl(), c);
    }

    public void setBackgroundColor(org.eclipse.swt.graphics.Color c) {
        backgroundColor = UiUtilities.getAwtBackground(getControl(), c);
    }

    public void setTitleForeground(org.eclipse.swt.graphics.Color c) {
        titleForegroundColor = UiUtilities.getAwtForeground(getControl(), c);
    }

    public void setTitleBackground(org.eclipse.swt.graphics.Color c) {
        titleBackgroundColor = UiUtilities.getAwtBackground(getControl(), c);
    }

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

    public ISelection getSelection() {
        return StructuredSelection.EMPTY;
    }

    public void setSelection(ISelection sel) {
        // do nothing
    }

    public void setInput(IdentifiableObject obj) {
        if (obj != null) {
            if (!obj.getStringId().equals(currentPresentation) && operationHistory != null)
                operationHistory.dispose(undoContext, true, true, true);
            cleanUp();
            currentPresentation = obj.getStringId();
        }
        if (currentSystemCursor == SWT.CURSOR_APPSTARTING)
            setCanvasCursor(null, SWT.CURSOR_ARROW);
    }

    public Control getControl() {
        return canvas;
    }

    @Override
    public void setFocus() {
        canvas.setFocus();
        if (currentSystemCursor == SWT.CURSOR_APPSTARTING)
            setCanvasCursor(null, SWT.CURSOR_ARROW);
    }

    protected void hookContextMenu() {
        if (menuMgr == null) {
            menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
            menuMgr.setRemoveAllWhenShown(true);
            menuMgr.addMenuListener(new IMenuListener() {
                public void menuAboutToShow(IMenuManager manager) {
                    AbstractPresentationView.this.fillContextMenu(manager);
                }
            });
            canvas.setMenu(menuMgr.createContextMenu(canvas));
            getSite().registerContextMenu(menuMgr, this);
        }
    }

    protected void unhookContextMenu() {
        if (menuMgr != null) {
            menuMgr.removeAll();
            menuMgr.dispose();
            menuMgr = null;
        }
    }

    protected void fillContextMenu(IMenuManager manager) {
        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
    }

    protected void addWheelListener(double min, double max) {
        wheelListener = new InertiaMouseWheelListener();
        wheelListener.setMinScale(min);
        wheelListener.setMaxScale(max);
        canvas.addMouseWheelListener(wheelListener);
    }

    private CatalogAdapter catListener = new CatalogAdapter() {

        @Override
        public void assetsModified(final BagChange<Asset> changes, QueryField node) {
            if (node == null) {
                Shell shell = getSite().getShell();
                if (!shell.isDisposed())
                    shell.getDisplay().asyncExec(() -> {
                        if (!shell.isDisposed()) {
                            if (changes != null)
                                updatePresentation(changes.getChanged());
                            else
                                refresh();
                        }
                    });
            }
        }

        @Override
        public void catalogClosed(int mode) {
            if (mode != CatalogListener.EMERGENCY && mode != CatalogListener.SHUTDOWN)
                setInput(null);
        }
    };

    protected abstract void updatePresentation(Collection<? extends Asset> assets);

    protected void addCatalogListener() {
        addCatalogListener(catListener);
    }

    protected abstract void setColor(Control canvas);

    protected void show() {
        refresh();
        launchDecorator();
    }

    @Override
    public void refresh() {
        if (canvas != null && !canvas.isDisposed())
            canvas.getDisplay().asyncExec(() -> {
                if (canvas != null && !canvas.isDisposed())
                    canvas.redraw();
            });
    }

    @Override
    public void dispose() {
        Core.getCore().removeCatalogListener(catListener);
        if (wheelListener != null) {
            wheelListener.dispose();
            wheelListener = null;
        }
        if (panEventHandler != null) {
            panEventHandler.dispose();
            panEventHandler = null;
        }
        if (zoomEventHandler != null) {
            zoomEventHandler.dispose();
            zoomEventHandler = null;
        }
        cleanUp();
        super.dispose();
    }

    protected void cleanUp() {
        currentPresentation = null;
        for (Image image : images)
            image.dispose();
        images.clear();
    }

    protected void contributeToActionBars() {
        IViewSite viewSite = getViewSite();
        IActionBars bars = viewSite.getActionBars();
        fillLocalPullDown(bars.getMenuManager());
        fillLocalToolBar(bars.getToolBarManager());
        new UndoRedoActionGroup(viewSite, undoContext, true).fillActionBars(bars);
    }

    protected void fillLocalToolBar(IToolBarManager manager) {
        manager.add(propertiesAction);
        manager.add(synchronizeAction);
        manager.add(new Separator());
    }

    protected void fillLocalPullDown(IMenuManager manager) {
        manager.add(new Separator());
        manager.add(propertiesAction);
    }

    protected void setPanAndZoomHandlers(double panSpeedOffset, double zoomSpeedOffset) {
        PNode[] workarea = getWorkArea();
        canvas.removeInputEventListener(canvas.getPanEventHandler());
        int horiztontalMargins = getHoriztontalMargins();
        panEventHandler = new GalleryPanEventHandler(this, workarea, -horiztontalMargins, surfaceBounds.y,
                surfaceBounds.width - horiztontalMargins, surfaceBounds.height + surfaceBounds.y, getPanDirection(),
                FORCE_PAN_MASK, panSpeedOffset, PAN_SENSITIVITY, HACCEL, VACCEL);
        canvas.addInputEventListener(panEventHandler);
        canvas.removeInputEventListener(canvas.getZoomEventHandler());
        zoomEventHandler = new GalleryZoomEventHandler(this, workarea, zoomSpeedOffset);
        zoomEventHandler.setMinScale(wheelListener.getMinScale());
        zoomEventHandler.setMaxScale(wheelListener.getMaxScale());
        canvas.addInputEventListener(zoomEventHandler);
    }

    protected void pan(PInputEvent event) {
        PCamera camera = canvas.getCamera();
        PBounds viewBounds = camera.getViewBounds();
        int f = event.isControlDown() ? 5 : 1;
        char keyChar = event.getKeyChar();
        if (keyChar >= ' ')
            handleKeyChar(camera, keyChar, f);
        else {
            int keyCode = event.getKeyCode();
            switch (keyCode) {
            case SWT.ARROW_RIGHT:
                pan(camera, -30 * f, 0);
                break;
            case SWT.ARROW_LEFT:
                pan(camera, 30 * f, 0);
                break;
            case SWT.ARROW_UP:
                pan(camera, 0, 30 * f);
                break;
            case SWT.ARROW_DOWN:
                pan(camera, 0, -30 * f);
                break;
            case SWT.END:
                pan(camera, (int) (-viewBounds.getWidth() / 2) * f, 0);
                break;
            case SWT.HOME:
                pan(camera, (int) (viewBounds.getWidth() / 2) * f, 0);
                break;
            case SWT.PAGE_UP:
                pan(camera, 0, (int) (viewBounds.getHeight() / 2) * f);
                break;
            case SWT.PAGE_DOWN:
                pan(camera, 0, (int) (-viewBounds.getHeight() / 2) * f);
                break;
            default:
                handleKeyChar(camera, (char) (keyCode & 0xff), 5);
                break;
            }
        }
    }

    protected void handleKeyChar(PCamera camera, char keyChar, int f) {
        switch (keyChar) {
        case '+':
            zoom(canvas, 1d + 0.05d * f);
            break;
        case '-':
            zoom(canvas, 1d - 0.05d * f);
            break;
        case '*':
            toggleTransform(camera);
            break;
        }
    }

    private void zoom(final PSWTCanvas pcanvas, double scaleDelta) {
        PCamera camera = pcanvas.getCamera();
        double newScale = scaleDelta * camera.getViewScale();
        if (newScale >= wheelListener.getMinScale() && newScale <= wheelListener.getMaxScale()) {
            camera.scaleView(scaleDelta);
            resetTransform();
        }
    }

    private void pan(PCamera camera, double deltaX, double deltaY) {
        PBounds viewBounds = camera.getViewBounds();
        double x = viewBounds.getX() - deltaX;
        PBounds clientArea = getClientAreaReference();
        if (x < clientArea.x && deltaX > 0)
            deltaX = Math.max(0, viewBounds.x - clientArea.x);
        if (x + viewBounds.getWidth() > clientArea.x + clientArea.width && deltaX < 0)
            deltaX = Math.min(0, viewBounds.x + viewBounds.width - (clientArea.x + clientArea.width));
        double y = viewBounds.getY() - deltaY;
        if (y < clientArea.y && deltaY > 0)
            deltaY = Math.max(0, viewBounds.y - clientArea.y);
        if (y + viewBounds.getHeight() > surfaceBounds.y + surfaceBounds.height && deltaY < 0)
            deltaY = Math.min(0, viewBounds.y + viewBounds.height - (clientArea.y + clientArea.height));
        camera.translateView(deltaX, deltaY);
        resetTransform();
    }

    protected abstract PBounds getClientAreaReference();

    protected abstract int getHoriztontalMargins();

    protected abstract int getPanDirection();

    protected abstract PNode[] getWorkArea();

    protected void addDropListener(final Control control) {
        final int ops = DND.DROP_MOVE | DND.DROP_COPY;
        final DropTarget target = new DropTarget(control, ops);
        final LocalSelectionTransfer transfer = LocalSelectionTransfer.getTransfer();
        target.setTransfer(new Transfer[] { transfer });
        target.addDropListener(new EffectDropTargetListener(control) {

            @Override
            public void dragEnter(DropTargetEvent event) {
                int detail = event.detail;
                event.detail = DND.DROP_NONE;
                if (!dbIsReadonly())
                    for (int i = 0; i < event.dataTypes.length; i++)
                        if (transfer.isSupportedType(event.dataTypes[i])) {
                            event.currentDataType = event.dataTypes[i];
                            event.detail = (detail & ops) == 0 ? DND.DROP_NONE : DND.DROP_COPY;
                            break;
                        }
                super.dragEnter(event);
            }

            @Override
            public void dragOver(DropTargetEvent event) {
                super.dragOver(event);
                if ((event.detail & DND.DROP_MOVE) != 0) {
                    org.eclipse.swt.graphics.Point pc = canvas.toControl(event.x, event.y);
                    if (findExhibit(toLocal(pc)) == null)
                        event.detail = DND.DROP_COPY;
                }
            }

            public void dragOperationChanged(DropTargetEvent event) {
                int detail = event.detail;
                event.detail = DND.DROP_NONE;
                if (transfer.isSupportedType(event.currentDataType) && !dbIsReadonly())
                    event.detail = (detail & ops) == 0 ? DND.DROP_NONE
                            : (detail & DND.DROP_COPY) == 0 ? DND.DROP_COPY : DND.DROP_MOVE;
            }

            public void drop(DropTargetEvent event) {
                if (transfer.isSupportedType(event.currentDataType) && !dbIsReadonly()) {
                    ISelection selection = transfer.getSelection();
                    if (selection instanceof AssetSelection) {
                        org.eclipse.swt.graphics.Point pc = canvas.toControl(event.x, event.y);
                        dropAssets(selection, pc, toLocal(pc), (event.detail & DND.DROP_MOVE) != 0);
                    }
                }
            }

            private Point2D toLocal(org.eclipse.swt.graphics.Point pc) {
                PCamera camera = canvas.getCamera();
                Point2D globalToLocal = new Point(pc.x, pc.y);
                camera.globalToLocal(globalToLocal);
                camera.localToView(globalToLocal);
                surface.parentToLocal(globalToLocal);
                return globalToLocal;
            }
        });
    }

    private PPresentationItem findExhibit(Point2D position) {
        double x = position.getX();
        for (ListIterator<?> iterator = surface.getChildrenIterator(); iterator.hasNext();) {
            Object container = iterator.next();
            if (container instanceof PContainer)
                for (ListIterator<?> iterator2 = ((PContainer) container).getChildrenIterator(); iterator2
                        .hasNext();) {
                    Object object = iterator2.next();
                    if (object instanceof PPresentationItem) {
                        double p = ((PPresentationItem) object).getOffset().getX();
                        if (p < x) {
                            p += ((PPresentationItem) object).getBoundsReference().width;
                            if (p >= x)
                                return (PPresentationItem) object;
                        }
                    }
                }
        }
        return null;
    }

    protected abstract void dropAssets(ISelection selection, org.eclipse.swt.graphics.Point point,
            Point2D globalToLocal, boolean replace);

    public IStatus performOperation(IUndoableOperation op) {
        if (operationHistory == null) {
            operationHistory = PlatformUI.getWorkbench().getOperationSupport().getOperationHistory();
            operationHistory.addOperationHistoryListener(this);
        }
        op.addContext(undoContext);
        try {
            return operationHistory.execute(op, null, this);
        } catch (ExecutionException e) {
            return new Status(IStatus.ERROR, UiActivator.PLUGIN_ID,
                    NLS.bind(Messages.getString("AbstractPresentationView.cannot_execute_operation"), op //$NON-NLS-1$
                            .getLabel()),
                    e);
        }
    }

    public void storeSafelyAndUpdateIndex(Object toBeDeleted, final Object toBeStored, Object toBeIndexed) {
        final IDbManager dbManager = Core.getCore().getDbManager();
        dbManager.safeTransaction(toBeDeleted, toBeStored);
        if (!dbManager.getMeta(true).getNoIndex()) {
            if (toBeIndexed instanceof String)
                OperationJob.executeOperation(new UpdateTextIndexOperation(new String[] { (String) toBeIndexed }),
                        this);
            else if (toBeIndexed instanceof Collection<?>) {
                @SuppressWarnings("unchecked")
                Collection<String> assetIds = (Collection<String>) toBeIndexed;
                if (!assetIds.isEmpty())
                    OperationJob.executeOperation(
                            new UpdateTextIndexOperation(assetIds.toArray(new String[assetIds.size()])), this);
            }
        }
    }

    protected void updateSurfaceBounds(double w, double h) {
        boolean changed = false;
        PBounds bounds = surface.getBoundsReference();
        double x = bounds.getX();
        if (w > bounds.getWidth() + x) {
            surface.setWidth(w - x);
            surfaceBounds.width = (int) (w - x);
            changed = true;
        }
        double y = bounds.getY();
        if (h > bounds.getHeight() - y) {
            surface.setHeight(h - y);
            surfaceBounds.height = (int) (h - y);
            changed = true;
        }
        if (changed) {
            int horiztontalMargins = getHoriztontalMargins();
            panEventHandler.setSurfaceBounds(-horiztontalMargins, surfaceBounds.y,
                    surfaceBounds.width - horiztontalMargins, surfaceBounds.height + surfaceBounds.y);
        }
    }

    public String getCurrentPresentation() {
        return currentPresentation;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == IPresentationItem.class) {
            PNode par = pickedNode;
            while (par != null && !(par instanceof IPresentationItem))
                par = par.getParent();
            return par;
        } else if (adapter == AssetSelection.class) {
            if (pickedNode != null) {
                IPresentationItem par = (IPresentationItem) getAdapter(IPresentationItem.class);
                if (par != null && par.getAssetId() != null) {
                    AssetImpl asset = Core.getCore().getDbManager().obtainAsset(par.getAssetId());
                    if (asset != null)
                        return new AssetSelection(asset);
                }
            }
            return AssetSelection.EMPTY;
        }
        return super.getAdapter(adapter);
    }

    protected PNode getPickedNode() {
        return pickedNode;
    }

    protected void setPickedNode(PNode pickedNode) {
        this.pickedNode = pickedNode;
        synchronize();
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int code = e.keyCode & SWT.MODIFIER_MASK;
        if (code != 0) {
            boolean zooming = false, panning = false;
            int zoomKey = Platform.getPreferencesService().getInt(UiActivator.PLUGIN_ID,
                    PreferenceConstants.ZOOMKEY, PreferenceConstants.ZOOMALT, null);
            switch (zoomKey) {
            case PreferenceConstants.ZOOMALT:
                zooming = code == SWT.ALT;
                panning = code == SWT.SHIFT;
                break;
            case PreferenceConstants.ZOOMSHIFT:
                zooming = code == SWT.SHIFT;
                panning = code == SWT.ALT;
                break;
            case PreferenceConstants.ZOOMRIGHT:
            case PreferenceConstants.NOZOOM:
                panning = (code & (SWT.ALT | SWT.SHIFT)) != 0;
                break;
            }
            if (zooming) {
                setCanvasCursor(CURSOR_MPLUS, -1);
                return;
            }
            if (panning) {
                setCanvasCursor(CURSOR_GRABBING, -1);
                return;
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        if ((e.keyCode & (SWT.ALT | SWT.SHIFT)) != 0)
            setObjectCursor(findObject(mouseX, mouseY), 0, CURSOR_OPEN_HAND);
    }

    protected void installHandleEventHandlers(final PNode node, boolean relative, boolean restricted,
            final PNode presentationObject) {
        AbstractHandleDragSequenceEventHandler handleDragger = createDragSequenceEventHandler(node, relative,
                restricted, presentationObject);
        PInputEventFilter eventFilter = new PInputEventFilter(InputEvent.BUTTON1_MASK, FORCE_PAN_MASK);
        handleDragger.setEventFilter(eventFilter);
        eventFilter.setMarksAcceptedEventsAsHandled(true);
        eventFilter.setAcceptsMouseEntered(false);
        eventFilter.setAcceptsMouseExited(false);
        eventFilter.setAcceptsMouseMoved(false);
        node.addInputEventListener(handleDragger);
    }

    protected abstract AbstractHandleDragSequenceEventHandler createDragSequenceEventHandler(PNode node,
            boolean relative, boolean restricted, PNode presentationObject);

    public void resetTransform() {
        oldTransform = null;
    }

    protected void toggleTransform(PCamera camera) {
        if (oldTransform == null) {
            oldTransform = camera.getViewTransform();
            camera.setViewTransform(new AffineTransform());
        } else {
            camera.setViewTransform(oldTransform);
            resetTransform();
        }
    }

    protected void endTask() {
        ((GridData) progressBar.getLayoutData()).heightHint = 1;
        progressBar.setVisible(false);
        try {
            progressBar.getParent().layout();
        } catch (Exception e) {
            // because of occasional piccolo crash
        }
    }

    protected void beginTask(int n) {
        ((GridData) progressBar.getLayoutData()).heightHint = PROGRESS_THICKNESS;
        progressBar.setVisible(true);
        try {
            progressBar.getParent().layout();
        } catch (Exception e) {
            // because of occasional piccolo crash
        }
        progressBar.beginTask(n);
    }

    protected void addCommonContextActions(IMenuManager manager) {
        manager.add(new Separator());
        manager.add(showFullscreenAction);
        if (!dbIsReadonly()) {
            IMenuManager modifyMenu = new MenuManager(Messages.getString("AbstractPresentationView.modify"), //$NON-NLS-1$
                    "modify"); //$NON-NLS-1$
            manager.add(modifyMenu);
            modifyMenu.add(editAction);
            modifyMenu.add(editWithAction);
            modifyMenu.add(rotateLeftAction);
            modifyMenu.add(rotateRightAction);
            if (exhibitPropertiesAction != null) {
                modifyMenu.add(new Separator());
                modifyMenu.add(exhibitPropertiesAction);
            }
            if (deleteAction != null)
                manager.add(deleteAction);
        }
        manager.add(new Separator());
        manager.add(showInFolderAction);
        AssetSelection assetSelection = (AssetSelection) getAdapter(AssetSelection.class);
        if (hasTimeLine())
            manager.add(showInTimeLineAction);
        if (showInMapAction != null && hasGeocode(assetSelection))
            manager.add(showInMapAction);
        if (hasVoiceNote(assetSelection))
            manager.add(voiceNoteAction);
    }

    @Override
    protected void registerCommands() {
        registerCommand(showInFolderAction, IZoomCommandIds.ShowInFolder);
        registerCommand(showInTimeLineAction, IZoomCommandIds.ShowInTimeline);
        registerCommand(showInMapAction, IZoomCommandIds.ShowInMap);
        registerCommand(showFullscreenAction, IZoomCommandIds.ViewImage);
        registerCommand(editAction, IZoomCommandIds.EditCommand);
        registerCommand(editWithAction, IZoomCommandIds.EditWithCommand);
        registerCommand(rotateLeftAction, IZoomCommandIds.RotateAntiClockwiseCommand);
        registerCommand(rotateRightAction, IZoomCommandIds.RotateClockwiseCommand);
        registerCommand(voiceNoteAction, IZoomCommandIds.PlayVoiceNote);
        registerCommand(deleteAction, IZoomCommandIds.DeleteCommand);
        registerCommand(cutAction, IWorkbenchCommandConstants.EDIT_CUT);
        registerCommand(pasteAction, IWorkbenchCommandConstants.EDIT_PASTE);
        super.registerCommands();
    }

    @Override
    public void updateActions(boolean force) {
        if (isVisible() || force) {
            AssetSelection selection = (AssetSelection) getAdapter(AssetSelection.class);
            boolean writable = !dbIsReadonly();
            int count = selection.size();
            int localCount = selection.getLocalAssets().size();
            boolean one = count == 1;
            boolean localOne = localCount == 1;
            boolean localSelected = localCount > 0;
            editAction.setEnabled(localSelected);
            editWithAction.setEnabled(localSelected);
            showInFolderAction.setEnabled(localOne);
            showInTimeLineAction.setEnabled(localOne && hasTimeLine());
            if (exhibitPropertiesAction != null)
                exhibitPropertiesAction.setEnabled(one);
            voiceNoteAction.setEnabled(localOne && hasVoiceNote(selection));
            rotateLeftAction.setEnabled(localSelected && writable && isMedia(selection, QueryField.PHOTO, true));
            rotateRightAction.setEnabled(localSelected && writable && isMedia(selection, QueryField.PHOTO, true));
            showFullscreenAction.setEnabled(one);
            if (showInMapAction != null)
                showInMapAction.setEnabled(count > 0);
            pasteAction.setEnabled(!clipboard.isEmpty());
            gotoLastSelectedAction.setEnabled(recentSelection != null);
            super.updateActions(count, localCount);
        }
    }

    protected void setCue(Cursor cue) {
        if (this.cue != null)
            this.cue.dispose();
        this.cue = cue;
    }

    public void historyNotification(OperationHistoryEvent event) {
        if (event.getEventType() == OperationHistoryEvent.UNDONE
                || event.getEventType() == OperationHistoryEvent.REDONE)
            refreshAfterHistoryEvent(event.getOperation());
    }

    @Override
    protected void updateStatusLine() {
        String statusMessage = getStatusMessage();
        if (statusMessage != null)
            setStatusMessage(statusMessage, false);
    }

    protected abstract String getStatusMessage();

    protected abstract void refreshAfterHistoryEvent(IUndoableOperation operation);

    public abstract String getId();

}