/**********************************************************************
 * Copyright (c) 2005, 2016 IBM Corporation, Ericsson
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Bernd Hufmann - Updated for TMF
 **********************************************************************/

package org.eclipse.tracecompass.tmf.ui.views.uml2sd;

import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.util.Messages;

/**
 * ScrollView widget provides a scrolling area with on-demand scroll bars.
 * Overview scrollable panel can be used (@see setOverviewEnabled()).
 *
 * @author Eric Miravete
 * @version 1.0
 */
public class ScrollView extends Composite {
    // ------------------------------------------------------------------------
    // Constants
    // ------------------------------------------------------------------------

    /**
     * Scroll bar mode AUTO
     */
    public static final int AUTO = 0;
    /**
     * Scroll bar mode ALWAYS_ON
     */
    public static final int ALWAYS_ON = 1;
    /**
     * Scroll bar mode ALWAYS_OFF
     */
    public static final int ALWAYS_OFF = 2;
    /**
     * Bit mask for visible vertical scroll bar
     */
    public static final int VBAR = 0x01;
    /**
     * Bit mask for visible horizontal scroll bar
     */
    public static final int HBAR = 0x02;

    private static final int DEFAULT_H_SCROLL_INCREMENT = 10;
    private static final int DEFAULT_V_SCROLL_INCREMENT = 10;
    private static final int DEFAULT_AUTO_SCROLL_PERIOD = 75;
    private static final int DEFAULT_OVERVIEW_SIZE = 100;

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    /**
     * Value of the contents height property.
     */
    private int fContentsHeight = 0;
    /**
     * Value of the contents width property.
     */
    private int fContentsWidth = 0;
    /**
     * Value of the contents x coordinate property
     */
    private int fContentsX = 0;
    /**
     * Value of the contents y coordinate property
     */
    private int fContentsY = 0;
    /**
     * Scroll bar mode of horizontal scroll bar.
     */
    private int fHorScrollbarMode = AUTO;
    /**
     * Scroll bar mode of vertical scroll bar.
     */
    private int fVertScrollbarMode = AUTO;
    /**
     * Increment for the horizontal scroll bar.
     */
    private int fHorScrollbarIncrement = DEFAULT_H_SCROLL_INCREMENT;
    /**
     * Increment for the vertical scroll bar.
     */
    private int fVertScrollbarIncrement = DEFAULT_V_SCROLL_INCREMENT;
    /**
     * Flag whether auto scroll is enabled or not.
     */
    private boolean fAutoScrollEnabled = true;
    /**
     * Value of the auto scroll period.
     */
    private int fAutoScrollPeriod = DEFAULT_AUTO_SCROLL_PERIOD;
    /**
     * The local paint listener reference.
     */
    private PaintListener fLocalPaintListener = null;
    /**
     * The local mouse move listener reference.
     */
    private MouseMoveListener fLocalMouseMoveListener = null;
    /**
     * The local mouse listener reference.
     */
    private MouseListener fLocalMouseListener = null;
    /**
     * The local control listener reference.
     */
    private ControlListener fLocalControlListener = null;
    /**
     * The local key listener reference.
     */
    private KeyListener fLocalKeyListener = null;
    // Canvas for vertical/horizontal Scroll Bar only ... because new ScrollBar() does works.
    /**
     * Canvas for horizontal scroll bar.
     */
    private Canvas fHorScrollBar;
    /**
     * Canvas for vertical scroll bar.
     */
    private Canvas fVertScrollBar;
    /**
     * Canvas for the view control.
     */
    private Canvas fViewControl;
    /**
     * Control used in the bottom right corner @see setCornerControl() and @see setOverviewEnabled(true)
     */
    private Control fCornerControl;
    /**
     * Size of overview widget.
     */
    private int fOverviewSize = DEFAULT_OVERVIEW_SIZE; // default size for overview
    /**
     * Timer for auto_scroll feature
     */
    private AutoScroll fAutoScroll = null;
    /**
     * TimerTask for auto_scroll feature !=null when auto scroll is running
     */
    private Timer fAutoScrollTimer = null;
    /**
     * where mouse down appear on contents area (x coordinate)
     */
    private int fMouseDownX = -1;
    /**
     * where mouse down appear on contents area (y coordinate)
     */
    private int fMousDownY = -1;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Create a ScrollView, child of composite c. Both scroll bar have the mode AUTO. Auto scroll feature is enabled
     * using a delay of 250ms. Overview feature is not enabled by default (use setOverviewEnabled()).
     *
     * @param c The parent composite
     * @param style The SWT style bits @see SWT
     */
    public ScrollView(Composite c, int style) {
        this(c, style, true);
    }

    /**
     * Create a ScrollView, child of composite c. Both scroll bar have the mode AUTO. Auto scroll feature is enabled
     * using a delay of 250ms. Overview feature is not enabled by default (use setOverviewEnabled()).
     *
     * @param c The parent composite.
     * @param style The SWT style bits @see SWT
     * @param mouseWheel Flag to force scrollView to handles mouse wheel
     */
    public ScrollView(Composite c, int style, boolean mouseWheel) {
        super(c, SWT.NONE);

        fHorScrollBar = new Canvas(this, SWT.H_SCROLL);
        if (mouseWheel) {
            // force scroll bar to get mouse wheel, those scrollbar will be hidden
            fViewControl = new Canvas(this, style | SWT.H_SCROLL | SWT.V_SCROLL);
        } else {
            fViewControl = new Canvas(this, style);
        }
        fViewControl.setBackground(getBackground());
        // hide scroll bar as their are replaced by fHorScrollBar and fVertScrollBar.
        if (mouseWheel) {
            fViewControl.getVerticalBar().setVisible(false);
            fViewControl.getHorizontalBar().setVisible(false);
        }
        fVertScrollBar = new Canvas(this, SWT.V_SCROLL);

        setLayout(new SVLayout());

        fLocalPaintListener = event -> {
            // use clipping, to reduce cost of paint.
            Rectangle r = event.gc.getClipping();
            int cx = viewToContentsX(r.x);
            int cy = viewToContentsY(r.y);
            drawContents(event.gc, cx, cy, r.width, r.height);
        };
        fViewControl.addPaintListener(fLocalPaintListener);

        fLocalMouseMoveListener = e -> {
            int ox = e.x, oy = e.y;
            e.x = viewToContentsX(e.x);
            e.y = viewToContentsY(e.y);
            contentsMouseMoveEvent(e);
            e.x = ox;
            e.y = oy;
        };

        fViewControl.addMouseMoveListener(fLocalMouseMoveListener);

        MouseTrackListener localMouseTrackListener = new MouseTrackListener() {
            @Override
            public void mouseEnter(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                e.y = viewToContentsY(e.y);
                contentsMouseEnter(e);
                e.x = ox;
                e.y = oy;
            }

            @Override
            public void mouseHover(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                e.y = viewToContentsY(e.y);
                contentsMouseHover(e);
                e.x = ox;
                e.y = oy;
            }

            @Override
            public void mouseExit(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                e.y = viewToContentsY(e.y);
                contentsMouseExit(e);
                e.x = ox;
                e.y = oy;
            }

        };

        fViewControl.addMouseTrackListener(localMouseTrackListener);

        fLocalMouseListener = new MouseListener() {
            @Override
            public void mouseDoubleClick(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                e.y = viewToContentsY(e.y);
                contentsMouseDoubleClickEvent(e);
                e.x = ox;
                e.y = oy;
            }

            @Override
            public void mouseDown(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                fMouseDownX = e.x;
                e.y = viewToContentsY(e.y);
                fMousDownY = e.y;
                contentsMouseDownEvent(e);
                e.x = ox;
                e.y = oy;
            }

            @Override
            public void mouseUp(MouseEvent e) {
                int ox = e.x, oy = e.y;
                e.x = viewToContentsX(e.x);
                e.y = viewToContentsY(e.y);
                contentsMouseUpEvent(e);
                e.x = ox;
                e.y = oy;
                // here because class extending me can catch mouse Up and want to scroll...
                fMouseDownX = -1;
                fMousDownY = -1;
            }
        };
        fViewControl.addMouseListener(fLocalMouseListener);

        fLocalKeyListener = new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
                keyPressedEvent(e);
            }

            @Override
            public void keyReleased(KeyEvent e) {
                keyReleasedEvent(e);
            }
        };

        fViewControl.addKeyListener(fLocalKeyListener);

        getVerticalBar().addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                setContentsPos(fContentsX, getVerticalBar().getSelection());
                // need to change "hidden" vertical bar value ?
                // force focus on fViewControl so we got future mouse wheel's scroll events
                if (!fViewControl.isFocusControl()) {
                    fViewControl.setFocus();
                }
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                // Do nothing
            }
        });

        if (fViewControl.getVerticalBar() != null) {
            // add fViewControl hidden scrollbar listener to get mouse wheel ...
            fViewControl.getVerticalBar().addSelectionListener(new SelectionListener() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    ScrollBar b = fViewControl.getVerticalBar();
                    setContentsPos(fContentsX, b.getSelection());
                    // change "real" vertical bar selection too
                    getVerticalBar().setSelection(b.getSelection());
                }

                @Override
                public void widgetDefaultSelected(SelectionEvent e) {
                    // Do nothing
                }
            });
        }
        getHorizontalBar().addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                setContentsPos(getHorizontalBar().getSelection(), fContentsY);
                // need to change "real" horizontal bar too ?
                // force focus on fViewControl so we got future mouse wheel's scroll events
                if (!fViewControl.isFocusControl()) {
                    fViewControl.setFocus();
                }
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                // Do nothing
            }
        });
        if (fViewControl.getHorizontalBar() != null) {
            fViewControl.getHorizontalBar().addSelectionListener(new SelectionListener() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    ScrollBar b = fViewControl.getHorizontalBar();
                    setContentsPos(b.getSelection(), fContentsY);
                    // change "real" vertical bar selection too
                    getHorizontalBar().setSelection(b.getSelection());
                }

                @Override
                public void widgetDefaultSelected(SelectionEvent e) {
                    // Do nothing
                }
            });
        }

        addDisposeListener((e) -> {
            if (fAutoScroll != null) {
                fAutoScroll.cancel();
                fAutoScroll = null;
            }
            fViewControl = null;
            fVertScrollBar = null;
            fHorScrollBar = null;
            if (fCornerControl != null) {
                Object data = fCornerControl.getData();
                if (data instanceof Overview) {
                    ((Overview) data).dispose();
                }
            }
            fCornerControl = null;
        });
    }

    // ------------------------------------------------------------------------
    // Methods
    // ------------------------------------------------------------------------

    @Override
    public boolean setFocus() {
        return fViewControl.forceFocus();
    }

    @Override
    public void setCursor(Cursor cursor) {
        fViewControl.setCursor(cursor);
    }

    @Override
    public Rectangle getClientArea() {
        Rectangle area = fViewControl.getClientArea();
        /* Clamp the size of the returned area to 1x1 minimum */
        area.width = Math.max(area.width, 1);
        area.height = Math.max(area.height, 1);
        return area;
    }

    @Override
    public void setBackground(Color c) {
        super.setBackground(c);
        fViewControl.setBackground(c);
    }

    @Override
    public void setToolTipText(String text) {
        fViewControl.setToolTipText(text);
    }

    /**
     * Draw overview area, @see setOverviewEnabled. By default draw a rectangle corresponding to the visible area of
     * scroll view. You can redefine this method to draw the contents as drawContents does... ...in an other magnify
     * factor.
     *
     * @param gc GC to used to draw.
     * @param r Rectangle corresponding to the client area of overview.
     */
    protected void drawOverview(GC gc, Rectangle r) {
        int x = (int) (r.width * fContentsX / (float) fContentsWidth);
        int y = (int) (r.height * fContentsY / (float) fContentsHeight);
        int vw = getVisibleWidth();
        int vh = getVisibleHeight();
        int w = r.width - 1;
        if (fContentsWidth > vw) {
            w = (int) (r.width * vw / (float) fContentsWidth);
        }
        int h = r.height - 1;
        if (fContentsHeight > vh) {
            h = (int) (r.height * vh / (float) fContentsHeight);
        }

        gc.setForeground(getForeground());
        // too small rectangle ?
        if (w < 5 || h < 5) {
            // use a cross ...
            gc.drawLine(x, 0, x, r.height);
            gc.drawLine(0, y, r.width, y);
        } else {
            gc.drawRectangle(x, y, w, h);
        }
    }

    /**
     * Remove the local Listener and add the new listener.
     *
     * @param nlistener the new listener
     */
    public void replaceControlListener(ControlListener nlistener) {
        if (fLocalControlListener != null) {
            removeControlListener(fLocalControlListener);
            fLocalControlListener = null;
        }
        addControlListener(nlistener);
    }

    /**
     * Remove the local Listener and add the new listener.
     *
     * @param nlistener the new listener
     */
    public void replaceKeyListener(KeyListener nlistener) {
        if (fLocalKeyListener != null) {
            removeKeyListener(fLocalKeyListener);
            fLocalKeyListener = null;
        }
        addKeyListener(nlistener);
    }

    /**
     * Remove the local Listener and add the new listener.
     *
     * @param nlistener the new listener
     */
    public void replaceMouseListener(MouseListener nlistener) {
        if (fLocalMouseListener != null) {
            removeMouseListener(fLocalMouseListener);
            fLocalMouseListener = null;
        }
        fViewControl.addMouseListener(nlistener);
    }

    /**
     * Remove the local Listener and add the new listener.
     *
     * @param nlistener the new listener
     */
    public void replaceMouseMoveListener(MouseMoveListener nlistener) {
        if (fLocalMouseMoveListener != null) {
            removeMouseMoveListener(fLocalMouseMoveListener);
            fLocalMouseMoveListener = null;
        }
        fViewControl.addMouseMoveListener(nlistener);
    }

    /**
     * Remove the local Listener and add the new listener.
     *
     * @param nlistener the new listener
     */
    public void replacePaintListener(PaintListener nlistener) {
        if (fLocalPaintListener != null) {
            removePaintListener(fLocalPaintListener);
            fLocalPaintListener = null;
        }
        fViewControl.addPaintListener(nlistener);
    }

    /**
     * Access method for the contentsHeight property.
     *
     * @return the current value of the contentsHeight property
     */
    public int getContentsHeight() {
        return fContentsHeight;
    }

    /**
     * Access method for the contentsWidth property.
     *
     * @return the current value of the contentsWidth property
     */
    public int getContentsWidth() {
        return fContentsWidth;
    }

    /**
     * Access method for the contentsX property.
     *
     * @return the current value of the contentsX property
     */
    public int getContentsX() {
        return fContentsX;
    }

    /**
     * Access method for the contentsY property.
     *
     * @return the current value of the contentsY property
     */
    public int getContentsY() {
        return fContentsY;
    }

    /**
     * Determines if the dragAutoScroll property is true.
     *
     * @return <code>true<code> if the dragAutoScroll property is true
     */
    public boolean isDragAutoScroll() {
        return fAutoScrollEnabled;
    }

    /**
     * Sets the value of the dragAutoScroll property.
     *
     * @param aDragAutoScroll the new value of the dragAutoScroll property
     */
    public void setDragAutoScroll(boolean aDragAutoScroll) {
        fAutoScrollEnabled = aDragAutoScroll;
        if (!fAutoScrollEnabled && (fAutoScroll != null)) {
            fAutoScroll.cancel();
            fAutoScroll = null;
        }
    }

    /**
     * Change delay (in millisec) used for auto scroll feature.
     *
     * @param period new period between to auto scroll
     */
    public void setDragAutoScrollPeriod(int period) {
        fAutoScrollPeriod = Math.max(0, period);
    }

    /**
     * Return auto scroll period.
     *
     * @return The period
     */
    public int getDragAutoScrollPeriod() {
        return fAutoScrollPeriod;
    }

    /**
     * Access method for the hScrollBarMode property.
     *
     * @return the current value of the hScrollBarMode property
     */
    public int getHScrollBarMode() {
        return fHorScrollbarMode;
    }

    /**
     * Sets the value of the hScrollBarMode property.
     *
     * @param aHScrollBarMode the new value of the hScrollBarMode property
     */
    public void setHScrollBarMode(int aHScrollBarMode) {
        fHorScrollbarMode = aHScrollBarMode;
    }

    /**
     * Access method for the vScrollBarMode property.
     *
     * @return the current value of the vScrollBarMode property
     */
    public int getVScrollBarMode() {
        return fVertScrollbarMode;
    }

    /**
     * Sets the value of the vScrollBarMode property.
     *
     * @param aVScrollBarMode the new value of the vScrollBarMode property
     */
    public void setVScrollBarMode(int aVScrollBarMode) {
        fVertScrollbarMode = aVScrollBarMode;
    }

    /**
     * Return horizontal scroll bar increment, default:1
     *
     * @return The increment
     */
    public int getHScrollBarIncrement() {
        return fHorScrollbarIncrement;
    }

    /**
     * Return vertical scroll bar increment, default:1
     *
     * @return The increment
     */
    public int getVScrollBarIncrement() {
        return fVertScrollbarIncrement;
    }

    /**
     * Change horizontal scroll bar increment, minimum:1. Page increment is
     * always set to visible width.
     *
     * @param inc
     *            Increment value to set
     */
    public void setHScrollBarIncrement(int inc) {
        fHorScrollbarIncrement = Math.max(1, inc);
    }

    /**
     * Change vertical scroll bar increment, minimum:1. Page increment is always
     * set to visible height.
     *
     * @param inc
     *            Increment value to set
     */
    public void setVScrollBarIncrement(int inc) {
        fVertScrollbarIncrement = Math.max(1, inc);
    }

    /**
     * Enable or disable overview feature. Enabling overview, dispose and replace existing corner control by a button.
     * Clicking in it open overview, move mouse cursor holding button to move scroll view and release button to hide
     * overview. Tip: hold control and/or shift key while moving mouse when overview is open made fine scroll.
     *
     * @param value true to engage overview feature
     */
    public void setOverviewEnabled(boolean value) {
        if (isOverviewEnabled() == value) {
            return;
        }

        Control cc = null;
        if (value) {
            Button b = new Button(this, SWT.NONE);
            b.setText("+"); //$NON-NLS-1$
            Overview ovr = new Overview();
            ovr.useControl(b);
            b.setData(ovr);
            cc = b;
            b.setToolTipText(Messages.SequenceDiagram_OpenOverviewTooltip);
        }
        setCornerControl(cc);
    }

    /**
     * Change overview size (at ratio 1:1), default is 100
     *
     * @param size
     *            The new size
     */
    public void setOverviewSize(int size) {
        fOverviewSize = Math.abs(size);
    }

    /**
     * Returns whether the overview is enabled or not.
     *
     * @return true is overview feature is enabled
     */
    public boolean isOverviewEnabled() {
        if (fCornerControl instanceof Button) {
            Object data = ((Button) fCornerControl).getData();
            // overview alreay
            if (data instanceof Overview) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the overview size at ratio 1:1.
     *
     * @return current overview size at ratio 1:1
     */
    public int getOverviewSize() {
        return fOverviewSize;
    }

    /**
     * Returns control used to display view (might not be this object). Use this control to add/remove listener on the
     * draw area.
     *
     * @return control used to display view (might not be this object).
     */
    public Control getViewControl() {
        return fViewControl;
    }

    /**
     * Called when the mouse enter the ScrollView area
     *
     * @param e
     *            Mouse event
     */
    protected void contentsMouseExit(MouseEvent e) {
        // Do nothing
    }

    /**
     * Called when the mouse enter the ScrollView area after and system defined
     * time
     *
     * @param e
     *            Mouse event
     */
    protected void contentsMouseHover(MouseEvent e) {
        // Do nothing
    }

    /**
     * Called when the mouse enter the ScrollView area
     *
     * @param e
     *            Mouse event
     */
    protected void contentsMouseEnter(MouseEvent e) {
        // Do nothing
    }

    /**
     * Called when user double on contents area.
     *
     * @param e
     *            Mouse event
     */
    protected void contentsMouseDoubleClickEvent(MouseEvent e) {
        // Do nothing
    }

    /**
     * Called when mouse is on contents area and button is pressed.
     *
     * @param e
     *            Mouse event
     */
    protected void contentsMouseDownEvent(MouseEvent e) {
        fMouseDownX = e.x;
        fMousDownY = e.y;
    }

    /**
     * TimerTask for auto scroll feature.
     */
    protected static class AutoScroll extends TimerTask {

        /** X delta */
        private int deltaX;

        /** Y delta */
        private int deltaY;

        /** ScrollView object */
        private ScrollView scrollView;

        /**
         * Constructor.
         *
         * @param sv
         *            ScrollView object to use
         * @param dx
         *            X delta
         * @param dy
         *            Y delta
         */
        public AutoScroll(ScrollView sv, int dx, int dy) {
            scrollView = sv;
            deltaX = dx;
            deltaY = dy;
        }

        @Override
        public void run() {
            final Display display = Display.getDefault();
            if ((display == null) || display.isDisposed()) {
                return;
            }
            display.asyncExec(() -> {
                if (!scrollView.isDisposed()) {
                    scrollView.scrollBy(deltaX, deltaY);
                }
            });
        }
    }

    /**
     * Called when mouse is on contents area and mode.
     *
     * @param event
     *            Mouse event
     */
    protected void contentsMouseMoveEvent(MouseEvent event) {
        if ((event.stateMask & SWT.BUTTON_MASK) != 0) {
            if (!fAutoScrollEnabled) {
                scrollBy(-(event.x - fMouseDownX), -(event.y - fMousDownY));
                return;
            }

            int sx = 0, sy = 0;

            int vRight = getContentsX() + getVisibleWidth();
            int vBottom = getContentsY() + getVisibleHeight();

            // auto scroll... ?
            if (event.x < getContentsX()) {
                sx = (getContentsX() - event.x);
                fMouseDownX = getContentsX();
            } else if (event.x > vRight) {
                sx = -event.x + vRight;
                fMouseDownX = vRight;
            }
            if (event.y < getContentsY()) {
                sy = (getContentsY() - event.y);
                fMousDownY = getContentsY();
            } else if (event.y > vBottom) {
                sy = -event.y + vBottom;
                fMousDownY = vBottom;
            }

            if (sx != 0 || sy != 0) {
                // start auto scroll...
                if (fAutoScroll == null) {
                    if (fAutoScrollTimer == null) {
                        fAutoScrollTimer = new Timer(true);
                    }
                    fAutoScroll = new AutoScroll(this, sx, sy);
                    fAutoScrollTimer.schedule(fAutoScroll, 0, fAutoScrollPeriod);
                } else {
                    fAutoScroll.deltaX = sx;
                    fAutoScroll.deltaY = sy;
                }
            } else {
                if (fAutoScroll != null) {
                    fAutoScroll.cancel();
                    fAutoScroll = null;
                }

                scrollBy(-(event.x - fMouseDownX), -(event.y - fMousDownY));
            }
        }
    }

    /**
     * Called when mouse is on contents area and button is released
     *
     * @param event
     *            Mouse event
     */
    protected void contentsMouseUpEvent(MouseEvent event) {
        // reset auto scroll if it's engaged
        if (fAutoScroll != null) {
            fAutoScroll.cancel();
            fAutoScroll = null;
        }
    }

    /**
     * Responsible to draw contents area. At least rectangle clipX must be
     * redrawn. This rectangle is given in contents coordinates. By default, no
     * paint is produced.
     *
     * @param gc
     *            Graphics context
     * @param clipx
     *            X clip
     * @param clipy
     *            Y clip
     * @param clipw
     *            W clip
     * @param cliph
     *            H clip
     */
    protected void drawContents(GC gc, int clipx, int clipy, int clipw, int cliph) {
        // Do nothing
    }

    /**
     * Change the size of the contents area.
     *
     * @param width new width of the area.
     * @param height new height of the area.
     */
    public void resizeContents(int width, int height) {
        int localWidth = width;
        int localHeight = height;

        if (localWidth < 0) {
            localWidth = 0;
        }
        if (localHeight < 0) {
            localHeight = 0;
        }

        int oldW = fContentsWidth;
        int oldH = fContentsHeight;

        if (localWidth == oldW && localHeight == oldH) {
            return;
        }

        fContentsWidth = localWidth;
        fContentsHeight = localHeight;

        if (oldW > localWidth) {
            int s = localWidth;
            localWidth = oldW;
            oldW = s;
        }

        int visWidth = getVisibleWidth();
        int visHeight = getVisibleHeight();
        if (oldW < visWidth) {
            if (localWidth > visWidth) {
                localWidth = visWidth;
            }
            fViewControl.redraw(getContentsX() + oldW, 0, localWidth - oldW, visHeight, true);
        }

        if (oldH > localHeight) {
            int s = localHeight;
            localHeight = oldH;
            oldH = s;
        }

        if (oldH < visHeight) {
            if (localHeight > visHeight) {
                localHeight = visHeight;
            }
            fViewControl.redraw(0, getContentsY() + oldH, visWidth, localHeight - oldH, true);
        }
        if (updateScrollBarVisiblity()) {
            layout();
        } else {
            updateScrollBarsValues();
        }
    }

    // redefined for internal use ..
    @Override
    public void redraw() {
        super.redraw();
        // ..need to redraw this already:
        fViewControl.redraw();
    }

    /**
     * @param delataX The delta in X
     * @param deltaY the delta in Y
     */
    public void scrollBy(int delataX, int deltaY) {
        setContentsPos(getContentsX() + delataX, getContentsY() + deltaY);
    }

    /**
     * Scroll to ensure point(in contents coordinates) is visible.
     *
     * @param px Point's x position
     * @param py Point's y position
     */
    public void ensureVisible(int px, int py) {
        int cx = getContentsX(), cy = getContentsY();
        int right = getContentsX() + getVisibleWidth();
        int bottom = getContentsY() + getVisibleHeight();
        if (px < getContentsX()) {
            cx = px;
        } else if (px > right) {
            cx = px - getVisibleWidth();
        }
        if (py < getContentsY()) {
            cy = py;
        } else if (py > bottom) {
            cy = py - getVisibleHeight();
        }
        setContentsPos(cx, cy);
    }

    /**
     * Make rectangle (x,y,w,h, in contents coordinates) visible. if rectangle cannot be completely visible, use
     * align flags.
     *
     * @param xValue x contents coordinates of rectangle.
     * @param yValue y contents coordinates of rectangle.
     * @param width width of rectangle.
     * @param height height of rectangle.
     * @param align bit or'ed SWT flag like SWT.LEFT,RIGHT,CENTER,TOP,BOTTOM,VERTICAL used only for bigger rectangle
     *            than visible area. By default CENTER/VERTICAL
     */
    public void ensureVisible(int xValue, int yValue, int width, int height, int align) {
        ensureVisible(xValue, yValue, width, height, align, false);
    }

    /**
     * Make rectangle (xValue,yValue,width,height, in contents coordinates) visible. if rectangle cannot be completely visible, use
     * align flags.
     *
     * @param xValue x contents coordinates of rectangle.
     * @param yValue y contents coordinates of rectangle.
     * @param width width of rectangle.
     * @param height height of rectangle.
     * @param align bit or'ed SWT flag like SWT.LEFT,RIGHT,CENTER,TOP,BOTTOM,VERTICAL used only for bigger rectangle
     *            than visible area. By default CENTER/VERTICAL
     * @param forceAlign force alignment for rectangle smaller than the visible area
     */
    protected void ensureVisible(int xValue, int yValue, int width, int height, int align, boolean forceAlign) {

        int localX = xValue;
        int localY = yValue;
        int localWidth = width;
        int localHeight = height;

        if (localWidth < 0) {
            localX = localX + localWidth;
            localWidth = -localWidth;
        }
        if (localHeight < 0) {
            localY = localY + localHeight;
            localHeight = -localHeight;
        }
        int hbar = getHorizontalBarHeight();
        int vbar = getVerticalBarWidth();
        int cx = getContentsX();
        int cy = getContentsY();
        int right = getContentsX() + getVisibleWidth() - vbar;
        int bottom = getContentsY() + getVisibleHeight() - hbar;
        boolean alignH = false, alignV = false;

        if (localX < getContentsX()) {
            cx = localX;
        } else if (localX + localWidth > right) {
            cx = localX - localWidth;
        }
        if (localY < getContentsY()) {
            cy = localY;
        } else if (localY + localHeight > bottom) {
            cy = localY - localHeight;
        }

        if (localWidth > getVisibleWidth()) {
            alignH = true;
        }
        if (localHeight > getVisibleHeight()) {
            alignV = true;
        }
        // compute alignment on visible area horizontally
        if (alignH || (forceAlign && localX + localWidth > right)) {
            // use align flags
            if ((align & SWT.LEFT) != 0) {
                cx = localX;
            } else if ((align & SWT.RIGHT) != 0) {
                cx = right - localWidth;
            } else { // center
                cx = localX + (localWidth - getVisibleWidth()) / 2;
            }
        }
        // compute alignment on visible area vertically
        if (alignV || (forceAlign && localY + localHeight > bottom)) {
            // use align flags
            if ((align & SWT.TOP) != 0) {
                cy = localY;
            } else if ((align & SWT.BOTTOM) != 0) {
                cy = bottom - localHeight;
            } else { // center
                cy = localY + (localHeight - getVisibleHeight()) / 2;
            }
        }
        setContentsPos(cx, cy);
    }

    /**
     * Returns true if point is visible (expressed in contents coordinates).
     *
     * @param px Point's x position
     * @param py Point's y position
     * @return true if point is visible (expressed in contents coordinates)
     */
    public boolean isVisible(int px, int py) {
        if (px < getContentsX()) {
            return false;
        }
        if (py < getContentsY()) {
            return false;
        }
        if (px > (getContentsX() + getVisibleWidth())) {
            return false;
        }
        return (py <= (getContentsY() + getVisibleHeight()));
    }

    /**
     * Returns true if rectangle if partially visible.
     *
     * @param xValue x contents coordinates of rectangle.
     * @param yValue y contents coordinates of rectangle.
     * @param width width of rectangle.
     * @param height height of rectangle.
     * @return true if rectangle if partially visible.
     */
    public boolean isVisible(int xValue, int yValue, int width, int height) {
        if (xValue + width < getContentsX()) {
            return false;
        }
        if (yValue + height < getContentsY()) {
            return false;
        }
        int vr = getContentsX() + getVisibleWidth();
        int vb = getContentsY() + getVisibleHeight();
        if (xValue > vr) {
            return false;
        }
        return (yValue <= vb);
    }

    /**
     * Returns visible part of rectangle, or null if rectangle is not visible.
     * Rectangle is expressed in contents coordinates.
     *
     * @param xValue
     *            x contents coordinates of rectangle.
     * @param yValue
     *            y contents coordinates of rectangle.
     * @param width
     *            width of rectangle.
     * @param height
     *            height of rectangle.
     * @return visible part of rectangle, or null if rectangle is not visible.
     */
    public Rectangle getVisiblePart(int xValue, int yValue, int width, int height) {
        if (xValue + width < getContentsX()) {
            return null;
        }
        if (yValue + height < getContentsY()) {
            return null;
        }
        int vr = getContentsX() + getVisibleWidth();
        int vb = getContentsY() + getVisibleHeight();
        if (xValue > vr) {
            return null;
        }
        if (yValue > vb) {
            return null;
        }
        int rr = xValue + width, rb = yValue + height;
        int nl = Math.max(xValue, getContentsX()), nt = Math.max(yValue, getContentsY()), nr = Math.min(rr, vr), nb = Math.min(rb, vb);
        return new Rectangle(nl, nt, nr - nl, nb - nt);
    }

    /**
     * Returns the visible part for given rectangle.
     *
     * @param rect A rectangle
     *
     * @return gets visible part of rectangle (or <code>null</code>)
     */
    public final Rectangle getVisiblePart(Rectangle rect) {
        if (rect == null) {
            return null;
        }
        return getVisiblePart(rect.x, rect.y, rect.width, rect.height);
    }

    /**
     * Change top left position of visible area. Check if the given point is inside contents area.
     *
     * @param xValue x contents coordinates of visible area.
     * @param yValue y contents coordinates of visible area.
     * @return true if view really moves
     */
    public boolean setContentsPos(int xValue, int yValue) {
        int nx = xValue, ny = yValue;
        if (getVisibleWidth() >= getContentsWidth()) {
            nx = 0;
        } else {
            if (xValue < 0) {
                nx = 0;
            } else if (xValue + getVisibleWidth() > getContentsWidth()) {
                nx = getContentsWidth() - getVisibleWidth();
            }
        }
        if (getVisibleHeight() >= getContentsHeight()) {
            ny = 0;
        } else {
            if (yValue <= 0) {
                ny = 0;
            } else if (yValue + getVisibleHeight() > getContentsHeight()) {
                ny = getContentsHeight() - getVisibleHeight();
            }
        }
        // no move
        if (nx == fContentsX && ny == fContentsY) {
            return false;
        }
        fContentsX = nx;
        fContentsY = ny;
        updateScrollBarsValues();
        // ? find smallest area to redraw only them ?
        fViewControl.redraw();
        return true;
    }

    @Override
    public ScrollBar getVerticalBar() {
        return fVertScrollBar.getVerticalBar();
    }

    @Override
    public ScrollBar getHorizontalBar() {
        return fHorScrollBar.getHorizontalBar();
    }

    /**
     * Compute visibility of vertical/horizontal bar using given width/height and current visibility (i.e. is bar size are already in
     * for_xxx)
     * @param forWidth width of foreground
     * @param forHeight height of foreground
     * @param currHorVis The current visibility state of horizontal scroll bar
     * @param currVertvis The current visibility state of vertical scroll bar
     * @return <code>true</code> if visibility changed else <code>false</code>
     */
    public int computeBarVisibility(int forWidth, int forHeight, boolean currHorVis, boolean currVertvis) {

        int localForWidth = forWidth;
        int vis = 0x00;
        switch (fVertScrollbarMode) {
        case ALWAYS_OFF:
            break;
        case ALWAYS_ON:
            vis |= VBAR;
            break;
        case AUTO:
            if (getContentsHeight() > forHeight) {
                vis = VBAR;
                // v bar size is already in for_width.
                if (!currVertvis) {// (curr_vis&0x01)==0)
                    localForWidth -= getVerticalBarWidth();
                }
            }
            break;
        default:
            break;
        }

        switch (fHorScrollbarMode) {
        case ALWAYS_OFF:
            break;
        case ALWAYS_ON:
            vis |= HBAR;
            break;
        case AUTO:
            if (getContentsWidth() > localForWidth) {
                vis |= HBAR;
                // h bar is not in for_height
                if ((!currHorVis) && (getContentsHeight() > (forHeight - getHorizontalBarHeight()))) {// (curr_vis&0x02)==0 )
                    vis |= VBAR;
                }
            }
            break;
        default:
            break;
        }
        return vis;
    }

    /**
     * Setup scroll bars visibility.
     *
     * @return True if one of visibility changed.
     */
    protected boolean updateScrollBarVisiblity() {
        boolean change = false;

        boolean currVertVis = fVertScrollBar.getVisible();
        boolean currHorVis = fHorScrollBar.getVisible();
        int barNewVis = computeBarVisibility(getVisibleWidth(), getVisibleHeight(), currHorVis, currVertVis);
        boolean newVertVis = (barNewVis & VBAR) != 0;
        boolean newHorVis = (barNewVis & HBAR) != 0;
        if (currVertVis ^ newVertVis) { // vertsb_.getVisible() )
            fVertScrollBar.setVisible(newVertVis);
            change = true;
        }
        if (currHorVis ^ newHorVis) {
            fHorScrollBar.setVisible(newHorVis);
            change = true;
        }

        // update corner control visibility:
        if (fCornerControl != null && change) {
            boolean vis = newVertVis || newHorVis;
            if (vis ^ fCornerControl.getVisible()) {
                fCornerControl.setVisible(vis);
                change = true; // but must be already the case
            }
        }
        return change;
    }

    /**
     * Setup scroll bar using contents, visible and scroll bar mode properties.
     */
    protected void updateScrollBarsValues() {
        /* update vertical scrollbar */
        ScrollBar b = getVerticalBar();
        if (b != null) {
            b.setMinimum(0);
            b.setMaximum(getContentsHeight());
            b.setThumb(getVisibleHeight());
            b.setPageIncrement(getVisibleHeight());
            b.setIncrement(fVertScrollbarIncrement);
            b.setSelection(getContentsY());
        }

        // update "hidden" vertical bar too
        b = fViewControl.getVerticalBar();
        if (b != null) {
            b.setMinimum(0);
            b.setMaximum(getContentsHeight());
            b.setThumb(getVisibleHeight());
            b.setPageIncrement(getVisibleHeight());
            b.setIncrement(fVertScrollbarIncrement);
            b.setSelection(getContentsY());
        }

        /* update horizontal scrollbar */
        b = getHorizontalBar();
        if (b != null) {
            b.setMinimum(0);
            b.setMaximum(getContentsWidth());
            b.setThumb(getVisibleWidth());
            b.setSelection(getContentsX());
            b.setPageIncrement(getVisibleWidth());
            b.setIncrement(fHorScrollbarIncrement);
        }
        // update "hidden" horizontal bar too
        b = fViewControl.getHorizontalBar();
        if (b != null) {
            b.setMinimum(0);
            b.setMaximum(getContentsWidth());
            b.setThumb(getVisibleWidth());
            b.setSelection(getContentsX());
            b.setPageIncrement(getVisibleWidth());
            b.setIncrement(fHorScrollbarIncrement);
        }
    }

    /**
     * Change the control used in the bottom right corner (between two scrollbar), if control is null reset previous
     * corner control. This control is visible only if at least one scrollbar is visible. Given control will be disposed
     * by ScrollView, at dispose() time, at next setCornetControl() call or when calling setOverviewEnabled(). Pay
     * attention calling this reset overview feature until setOverviewEnabled(true) if called.
     * @param control The control for the overview
     */
    public void setCornerControl(Control control) {
        if (fCornerControl != null) {
            fCornerControl.dispose();
        }
        fCornerControl = control;
        if (fCornerControl != null) {
            ScrollBar vb = getVerticalBar();
            ScrollBar hb = getHorizontalBar();
            boolean vis = vb.getVisible() || hb.getVisible();
            fCornerControl.setVisible(vis);
        }
    }

    /**
     * Transform (x,y) point in widget coordinates to contents coordinates.
     *
     * @param x The x widget coordinate.
     * @param y The y widget coordinate.
     * @return org.eclipse.swt.graphics.Point with content coordinates.
     */
    public final Point viewToContents(int x, int y) {
        return new Point(viewToContentsX(x), viewToContentsY(y));
    }

    /**
     * Transform x in widget coordinates to contents coordinates
     *
     * @param x The y widget coordinate.
     * @return the x content coordinate.
     */
    public int viewToContentsX(int x) {
        return fContentsX + x;
    }

    /**
     * Transform y in widget coordinates to contents coordinates
     *
     * @param y The y widget coordinate.
     * @return the y content coordinate.
     */
    public int viewToContentsY(int y) {
        return fContentsY + y;
    }

    /**
     * Transform (x,y) point from contents coordinates, to widget coordinates.
     *
     * @param x The x content coordinate.
     * @param y The y content coordinate.
     * @return coordinates widget area as.
     */
    public final Point contentsToView(int x, int y) {
        return new Point(contentsToViewX(x), contentsToViewY(y));
    }

    /**
     * Transform X axis coordinates from contents to widgets.
     *
     * @param x contents coordinate to transform.
     * @return x coordinate in widget area
     */
    public int contentsToViewX(int x) {
        return x - fContentsX;
    }

    /**
     * Transform Y axis coordinates from contents to widgets.
     *
     * @param y contents coordinate to transform
     * @return y coordinate in widget area
     */
    public int contentsToViewY(int y) {
        return y - fContentsY;
    }

    /**
     * Return the visible height of scroll view, might be > contentsHeight
     *
     * @return the visible height of scroll view, might be > contentsHeight()
     */
    public int getVisibleHeight() {
        return fViewControl.getClientArea().height;
    }

    /**
     * Return int the visible width of scroll view, might be > contentsWidth().
     *
     * @return int the visible width of scroll view, might be > contentsWidth()
     */
    public int getVisibleWidth() {
        return fViewControl.getClientArea().width;
    }

    /**
     * Add support for arrow key, scroll the ... scroll view. But you can
     * redefine this method for your convenience.
     *
     * @param event
     *            Keyboard event
     */
    protected void keyPressedEvent(KeyEvent event) {
        switch (event.keyCode) {
        case SWT.ARROW_UP:
            scrollBy(0, -getVisibleHeight());
            break;
        case SWT.ARROW_DOWN:
            scrollBy(0, +getVisibleHeight());
            break;
        case SWT.ARROW_LEFT:
            scrollBy(-getVisibleWidth(), 0);
            break;
        case SWT.ARROW_RIGHT:
            scrollBy(+getVisibleWidth(), 0);
            break;
        default:
            break;
        }
    }

    /**
     * Redefine this method at your convenience
     *
     * @param event The key event.
     */
    protected void keyReleasedEvent(KeyEvent event) {
        // Do nothing
    }

    /**
     * Returns vertical bar width, even if bar isn't visible.
     *
     * @return vertical bar width, even if bar isn't visible
     */
    public int getVerticalBarWidth() {
        // include vertical bar width and trimming of scrollable used
        int bw = fVertScrollBar.computeTrim(0, 0, 0, 0).width;
        return bw + 1;
    }

    /**
     * Returns horizontal bar height even if bar isn't visible.
     *
     * @return horizontal bar height even if bar isn't visible
     */
    public int getHorizontalBarHeight() {
        // include horiz. bar height and trimming of scrollable used
        int bh = fHorScrollBar.computeTrim(0, 0, 0, 0).height;
        // +1 because win32 H.bar need 1 pixel canvas size to appear ! (strange no ?)
        return bh + 1;
    }

    @Override
    public Rectangle computeTrim(int x, int y, int w, int h) {
        Rectangle r = new Rectangle(x, y, w, h);
        int barVis = computeBarVisibility(w, h, false, false);
        if ((barVis & VBAR) != 0) {
            r.width += getVerticalBarWidth();
        }
        if ((barVis & HBAR) != 0) {
            r.height += getHorizontalBarHeight();
        }
        return r;
    }

    /**
     *  Internal layout for ScrollView, handle scrollbars, drawzone and corner control
     */
    protected class SVLayout extends Layout {

        private static final int DEFAULT_X = 250;
        private static final int DEFAULT_Y = 250;
        private static final int MAX_SEEK = 10;
        private static final int MIN_SEEK = 0;

        /**
         * The seek value
         */
        private int seek = 0;
        /**
         * The do-it-not flag
         */
        private boolean dontLayout = false;

        @Override
        protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
            Point p = new Point(DEFAULT_X, DEFAULT_Y);
            if (fContentsWidth < p.x) {
                p.x = fContentsWidth;
            }
            if (fContentsHeight < p.y) {
                p.y = fContentsHeight;
            }
            return p;
        }

        @Override
        protected void layout(Composite composite, boolean flushCache) {
            if (dontLayout) {
                return;
            }
            seek++;
            if (seek > MAX_SEEK) {
                dontLayout = true;
            }

            Point cs = composite.getSize();
            int barVis = computeBarVisibility(cs.x, cs.y, false, false);
            boolean vbVis = (barVis & VBAR) != 0;
            boolean hbVis = (barVis & HBAR) != 0;
            fVertScrollBar.setVisible(vbVis);
            fHorScrollBar.setVisible(hbVis);
            int vbw = getVerticalBarWidth();
            int hbh = getHorizontalBarHeight();
            int wb = vbVis ? vbw : 0;
            int hb = hbVis ? hbh : 0;
            int cww = 0, cwh = 0;

            if (fCornerControl != null && (vbVis || hbVis)) { // corner_control_.getVisible())
                fCornerControl.setVisible(true);
                cww = vbw;
                cwh = hbh;
                if (wb == 0) {
                    wb = vbw;
                }
                if (hb == 0) {
                    hb = hbh;
                }
            } else if (vbVis && hbVis) {
                if (fCornerControl != null) {
                    fCornerControl.setVisible(false);
                }
                cww = vbw;
                cwh = hbh;
            }
            if (vbVis || hbVis) {
                updateScrollBarsValues();
            }

            int vw = cs.x - (vbVis ? vbw : 0);
            int vh = cs.y - (hbVis ? hbh : 0);
            int vbx = cs.x - wb;
            int hby = cs.y - hb;

            fViewControl.setBounds(0, 0, vw, vh);

            if (vbVis) {
                fVertScrollBar.setBounds(vbx, 0, wb, cs.y - cwh);
            }
            if (hbVis) {
                fHorScrollBar.setBounds(0, hby, cs.x - cww, hb);
            }
            if (fCornerControl != null && fCornerControl.getVisible()) {
                fCornerControl.setBounds(vbx, hby, vbw, hbh);
            }
            updateScrollBarsValues();

            seek--;
            if (seek == MIN_SEEK) {
                dontLayout = false;
            }
        }

        boolean isDontLayout() {
            return dontLayout;
        }

        void setSontLayout(boolean dontLayout) {
            this.dontLayout = dontLayout;
        }
    }

    // static must take place here... cursor is created once.
    private static volatile Cursor fOverviewCursor;

    /** Support for click-and-see overview shell on this ScrollView */
    protected class Overview {

        private static final  int REFRESH_FREQ = 4;

        /**
         *  factor for X from real and overview sizes, for mouse move speed.
         */
        private float fOverviewFactorX;

        /**
         *  factor for Y from real and overview sizes, for mouse move speed.
         */
        private float fOverviewFactorY;
        /**
         *  shell use to show overview
         */
        private Shell fOverview;
        /**
         * save mouse X cursor location for disappear();
         */
        private int fSaveCursorX;
        /**
         * save mouse Y cursor location for disappear();
         */
        private int fSaveCursorY;

        /**
         * Apply overview support on a control. Replace existing corner_widget
         *
         * @param control
         *            The control to use
         */
        public void useControl(Control control) {
            final Point pos = control.getLocation();
            control.addMouseListener(new MouseListener() {
                @Override
                public void mouseDoubleClick(MouseEvent e) {
                    // Do nothing
                }

                @Override
                public void mouseDown(MouseEvent e) {
                    overviewAppear(e.x, e.y);
                }

                @Override
                public void mouseUp(MouseEvent e) {
                    overviewDisappear();
                }
            });

            control.addFocusListener(new FocusListener() {

                @Override
                public void focusGained(FocusEvent e) {
                    // Do nothing
                }

                @Override
                public void focusLost(FocusEvent e) {
                    if (overviewing()) {
                        overviewDisappear(false);
                    }
                }

            });
            control.addKeyListener(new KeyListener() {

                @Override
                public void keyPressed(KeyEvent event) {
                    if (event.keyCode == 32 && !overviewing()) {
                        overviewAppear(pos.x, pos.y);
                    } else if (event.keyCode == 32) {
                        overviewDisappear();
                    }
                    if (event.keyCode == SWT.ARROW_DOWN) {
                        overviewMove(0, 1, event);
                    }

                    if (event.keyCode == SWT.ARROW_UP) {
                        overviewMove(0, -1, event);
                    }

                    if (event.keyCode == SWT.ARROW_RIGHT) {
                        overviewMove(1, 0, event);
                    }

                    if (event.keyCode == SWT.ARROW_LEFT) {
                        overviewMove(-1, 0, event);
                    }
                }

                @Override
                public void keyReleased(KeyEvent e) {
                    // Do nothing
                }
            });
            control.addMouseMoveListener(new MouseMoveListener() {
                private int refReshCount  = 0;
                @Override
                public void mouseMove(MouseEvent event) {
                    if (overviewing()) {
                        // Slow down the refresh
                        if (refReshCount % REFRESH_FREQ == 0) {
                            overviewMove(event);
                        }
                        refReshCount++;
                    }
                }
            });
        }

        /**
         * Dispose controls of overview
         */
        public void dispose() {
            if (fOverview != null) {
                fOverview.dispose();
            }
        }

        /**
         * @return true if overview is currently on screen
         */
        protected boolean overviewing() {
            return (fOverview != null && fOverview.isVisible());
        }

        /**
         * Process overview appear
         *
         * @param mx
         *            X coordinate
         * @param my
         *            Y coordinate
         */
        protected void overviewAppear(int mx, int my) {
            if (fOverview == null) {
                fOverview = new Shell(getShell(), SWT.ON_TOP | SWT.NO_BACKGROUND);
                fOverview.addPaintListener(e -> drawOverview(e.gc, fOverview.getClientArea()));
            }
            // always the same..
            fOverview.setForeground(fViewControl.getForeground());

            // get location of shell (in screeen coordinates)
            Point p = toGlobalCoordinates(fCornerControl, 0, 0);
            int x = p.x;
            int y = p.y;
            int w, h;
            h = fOverviewSize;
            w = h;
            Rectangle scr = getDisplay().getBounds();
            Point ccs = fCornerControl.getSize();
            try {
                if (fContentsWidth > fContentsHeight) {
                    float ratio = fContentsHeight / (float) fContentsWidth;
                    h = (int) (w * ratio);
                    if (h < ccs.y) {
                        h = ccs.y;
                    } else if (h >= scr.height / 2) {
                        h = scr.height / 2;
                    }
                } else {
                    float ratio = fContentsWidth / (float) fContentsHeight;
                    w = (int) (h * ratio);
                    if (w < ccs.x) {
                        w = ccs.x;
                    } else if (w >= scr.width / 2) {
                        w = scr.width / 2;
                    }
                }
                fOverviewFactorX = fContentsWidth / (float) w;
                fOverviewFactorY = fContentsHeight / (float) h;
            }
            // no contents size set ?
            catch (java.lang.ArithmeticException e) {
            }

            // try pop-up on button, extending to bottom right,
            if (x <= 0) {
                x = 1;
            }
            if (y <= 0) {
                y = 1;
            }
            x = x - w + ccs.x;
            y = y - h + ccs.y;
            fOverview.setBounds(x, y, w, h);
            fOverview.setVisible(true);
            fOverview.redraw();
            // mouse cursor disappear, so set invisible mouse cursor ...
            if (fOverviewCursor == null) {
                RGB rgb[] = { new RGB(0, 0, 0), new RGB(255, 0, 0) };
                PaletteData palette = new PaletteData(rgb);
                int s = 1;
                byte src[] = new byte[s * s];
                byte msk[] = new byte[s * s];
                for (int i = 0; i < s * s; ++i) {
                    src[i] = (byte) 0xFF;
                }
                ImageData i_src = new ImageData(s, s, 1, palette, 1, src);
                ImageData i_msk = new ImageData(s, s, 1, palette, 1, msk);
                fOverviewCursor = new Cursor(null, i_src, i_msk, 0, 0);
            }
            fCornerControl.setCursor(fOverviewCursor);
            // convert to global coordinates
            p = toGlobalCoordinates(fCornerControl, mx, my);
            fSaveCursorX = p.x;
            fSaveCursorY = p.y;

            Rectangle r = fOverview.getClientArea();
            int cx = (int) (r.width * fContentsX / (float) fContentsWidth);
            int cy = (int) (r.height * fContentsY / (float) fContentsHeight);

            // cx,cy to display's global coordinates
            p = toGlobalCoordinates(fOverview.getParent(), cx, cy);
        }

        /**
         * Process disappear of overview
         */
        protected void overviewDisappear() {
            overviewDisappear(true);
        }

        /**
         * Process disappear of overview
         * @param restoreCursorLoc A flag to restore cursor location
         */
        protected void overviewDisappear(boolean restoreCursorLoc) {
            if (fOverview == null) {
                return;
            }
            fOverview.setVisible(false);
            fCornerControl.setCursor(null);
            if (restoreCursorLoc) {
                getDisplay().setCursorLocation(fSaveCursorX, fSaveCursorY);
            }
            fOverview.dispose();
            fOverview = null;
        }

        /**
         * Process mouse move in overview
         * @param event The mouse event
         */
        protected void overviewMove(MouseEvent event) {
            Point p = toGlobalCoordinates(fCornerControl, event.x, event.y);
            int dx = p.x - fSaveCursorX;
            int dy = p.y - fSaveCursorY;
            overviewMove(dx, dy, event);
        }

        /**
         * Process mouse move event when overviewing
         *
         * @param dx The x coordinates delta
         * @param dy The y coordinates delta
         * @param event The typed event
         */
        protected void overviewMove(int dx, int dy, TypedEvent event) {
            boolean ctrl = false;
            boolean shift = false;

            if (event instanceof MouseEvent) {
                MouseEvent e = (MouseEvent) event;
                getDisplay().setCursorLocation(fSaveCursorX, fSaveCursorY);
                ctrl = (e.stateMask & SWT.CONTROL) != 0;
                shift = (e.stateMask & SWT.SHIFT) != 0;
            } else if (event instanceof KeyEvent) {
                KeyEvent e = (KeyEvent) event;
                ctrl = (e.stateMask & SWT.CONTROL) != 0;
                shift = (e.stateMask & SWT.SHIFT) != 0;
            }

            int cx = fContentsX;
            int cy = fContentsY;
            float fx = fOverviewFactorX;
            float fy = fOverviewFactorY;

            if (ctrl && shift) {
                if ((fx * 0.25f > 1) && (fy * 0.25 > 1)) {
                    fx = fy = 1.0f;
                } else {
                    fx *= 0.1f;
                    fy *= 0.1f;
                }
            } else if (ctrl) {
                fx *= 0.5f;
                fy *= 0.5f;
            } else if (shift) {
                fx *= 0.5f;
                fy *= 0.5f;
            }
            scrollBy((int) (fx * dx), (int) (fy * dy));
            if (cx != fContentsX || cy != fContentsY) {
                fOverview.redraw();
                fOverview.update(); // draw now !
            }
        }

        /**
         * Convert overview coordinates to global coordinates.
         *
         * @param loc
         *            the control reference
         * @param x
         *            The x coordinate to convert
         * @param y
         *            The y coordinate to convert
         * @return The new converted Point
         */
        protected Point toGlobalCoordinates(Control loc, int x, int y) {
            Point p = new Point(x, y);
            for (Control c = loc; c != null; c = c.getParent()) {
                // control might have client area with 'decorations'
                int trimX = 0, trimY = 0;
                // other kind of widget with trimming ??
                if (c instanceof Scrollable) {
                    Scrollable s = (Scrollable) c;
                    Rectangle rr = s.getClientArea();
                    Rectangle tr = s.computeTrim(rr.x, rr.y, rr.width, rr.height);
                    trimX = rr.x - tr.x;
                    trimY = rr.y - tr.y;
                }
                p.x += c.getLocation().x + trimX;
                p.y += c.getLocation().y + trimY;
            }
            return p;
        }
    }
}
