org.xmind.ui.dialogs.SmoothPopupDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.xmind.ui.dialogs.SmoothPopupDialog.java

Source

/* ******************************************************************************
 * Copyright (c) 2006-2012 XMind Ltd. and others.
 * 
 * This file is a part of XMind 3. XMind releases 3 and
 * above are dual-licensed under the Eclipse Public License (EPL),
 * which is available at http://www.eclipse.org/legal/epl-v10.html
 * and the GNU Lesser General Public License (LGPL), 
 * which is available at http://www.gnu.org/licenses/lgpl.html
 * See http://www.xmind.net/license.html for details.
 * 
 * Contributors:
 *     XMind Ltd. - initial API and implementation
 *******************************************************************************/
package org.xmind.ui.dialogs;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.util.UITimer;
import org.xmind.ui.viewers.ImageButton;

/**
 * @author Frank Shaka
 */
public class SmoothPopupDialog extends Window {

    private static Map<String, PopupGroup> groups = new HashMap<String, PopupGroup>();

    private static class PopupGroup {

        private Point initBottomRight = null;

        private Point bottomRight = null;

        private int width = 0;

        private List<SmoothPopupDialog> dialogs = new ArrayList<SmoothPopupDialog>();

        public Point getBottomRight() {
            return bottomRight;
        }

        public void setBottomRight(int right, int bottom) {
            this.initBottomRight = new Point(right, bottom);
            this.bottomRight = new Point(right, bottom);
        }

        public void add(SmoothPopupDialog dialog, int height, int width) {
            if (bottomRight == null)
                throw new IllegalStateException();

            dialogs.add(dialog);
            int top = bottomRight.y - height;
            if (top < Display.getCurrent().getClientArea().y) {
                this.bottomRight.x -= this.width;
                this.bottomRight.y = this.initBottomRight.y;
            } else {
                this.width = Math.max(this.width, width);
                this.bottomRight.y -= height;
            }
        }

        public void remove(SmoothPopupDialog dialog) {
            dialogs.remove(dialog);
            if (dialogs.isEmpty()) {
                initBottomRight = null;
                bottomRight = null;
            }
        }
    }

    private final class BorderFillLayout extends Layout {

        private int borderWidth;

        private int margin;

        public BorderFillLayout(int borderWidth) {
            this.borderWidth = borderWidth;
            this.margin = borderWidth + borderWidth;
        }

        protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
            Point size = computeContentSize();
            return new Point(size.x + margin, size.y + margin);
        }

        private Point computeContentSize() {
            if (targetSize != null) {
                return targetSize;
            } else if (getContents() != null) {
                return getContents().computeSize(DEFAULT_TARGET_SIZE.x, DEFAULT_TARGET_SIZE.y);
            }
            return DEFAULT_TARGET_SIZE;
        }

        protected void layout(Composite composite, boolean flushCache) {
            int x = borderWidth;
            int y = borderWidth;
            Rectangle clientArea = composite.getClientArea();
            if (targetSize != null) {
                clientArea.width = targetSize.x;
                clientArea.height = targetSize.y;
            }
            int width = clientArea.width;
            int height = clientArea.height;
            Control[] children = composite.getChildren();
            for (Control c : children) {
                c.setBounds(x, y, width - 2 * borderWidth, height - 2 * borderWidth);
            }
        }

    }

    protected class PullDownTask implements Runnable {

        Display display;

        boolean canceled = false;

        public PullDownTask(Display display) {
            this.display = display;
        }

        public void cancel() {
            this.canceled = true;
        }

        public boolean isCanceled() {
            return this.canceled;
        }

        public void run() {
            if (canceled || display.isDisposed())
                return;
            pullDown();
        }
    }

    protected static final String DEFAULT_BACKGROUDCOLOR_VALUE = "#e9e8e9"; //$NON-NLS-1$

    private static final int VERTICAL_SPEED = 3;

    private static final int ANIM_INTERVALS = 8;

    private static final GridDataFactory LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true, true);

    private static final GridDataFactory LAYOUTDATA_GRAB_HORIZONTAL = GridDataFactory.fillDefaults()
            .align(SWT.FILL, SWT.CENTER).grab(true, false);

    private static final GridDataFactory LAYOUTDATA_ALIGN_RIGHT = GridDataFactory.fillDefaults().align(SWT.END,
            SWT.CENTER);

    private static final GridLayoutFactory LAYOUT_CONTENTS = GridLayoutFactory.fillDefaults().numColumns(1)
            .margins(1, 1).extendedMargins(0, 0, 0, 0).spacing(1, 1);

    /**
     * Margin width (in pixels) to be used in layouts inside popup dialogs
     * (value is 0).
     */
    public final static int POPUP_MARGINWIDTH = 0;

    /**
     * Margin height (in pixels) to be used in layouts inside popup dialogs
     * (value is 0).
     */
    public final static int POPUP_MARGINHEIGHT = 0;

    /**
     * Vertical spacing (in pixels) between cells in the layouts inside popup
     * dialogs (value is 1).
     */
    public final static int POPUP_VERTICALSPACING = 1;

    /**
     * Vertical spacing (in pixels) between cells in the layouts inside popup
     * dialogs (value is 1).
     */
    public final static int POPUP_HORIZONTALSPACING = 1;

    /**
     * 
     */
    private static final GridLayoutFactory POPUP_LAYOUT_FACTORY = GridLayoutFactory.fillDefaults()
            .margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT).spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);

    private static final int POPUP_GAP = 3;

    /**
     * Border thickness in pixels.
     */
    private static final int BORDER_THICKNESS = 1;

    private static int STAY_DURATION = 5000;

    protected static ImageDescriptor IMG_CLOSE_NORMAL = null;

    private Point DEFAULT_TARGET_SIZE;

    private boolean showCloseButton = false;

    private Point targetSize = null;

    private String titleText = null;

    private Control dialogArea = null;

    private Point startingBottomRight = null;

    private boolean centerPopUp = false;

    private boolean popup = false;

    private UITimer timer = null;

    private int targetWidth = 0;

    private int targetHeight = 0;

    private int currentHeight = 0;

    private PullDownTask pullDownTask = null;

    private Control sourceControl = null;

    private Listener sourceControlMoveListener = null;

    private PopupGroup group = null;

    private int duration = STAY_DURATION;

    public SmoothPopupDialog(Shell parent, boolean showCloseButton, String titleText) {
        super(parent);
        setShellStyle(SWT.TOOL);
        setBlockOnOpen(false);
        this.showCloseButton = showCloseButton;
        this.titleText = titleText;

        Point size = parent.getSize();
        DEFAULT_TARGET_SIZE = new Point(size.x * 70 / 100, -1);
    }

    /**
     * 
     * @param stayDuration
     *            the duration this dialog will stay on the screen, in
     *            milliseconds
     */
    public void setDuration(int stayDuration) {
        this.duration = stayDuration;
    }

    public int getDuration() {
        return this.duration;
    }

    protected void configureShell(Shell shell) {
        Display display = shell.getDisplay();

        int border = getBorderThickness();
        shell.setLayout(new BorderFillLayout(border));

        if (border > 0) {
            Color borderColor = getBorderColor(display);
            if (borderColor == null)
                borderColor = display.getSystemColor(SWT.COLOR_GRAY);
            shell.setBackground(borderColor);
        }

        shell.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent event) {
                handleDispose();
            }
        });
    }

    private int getBorderThickness() {
        return ((getShellStyle() & SWT.NO_TRIM) == 0) ? 0 : BORDER_THICKNESS;
    }

    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        LAYOUT_CONTENTS.applyTo(composite);

        if (hasTitleArea()) {
            Control titleArea = createTitleArea(composite);
            LAYOUTDATA_GRAB_HORIZONTAL.applyTo(titleArea);
        }

        dialogArea = createDialogArea(composite);
        if (dialogArea.getLayoutData() == null) {
            LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea);
        }

        applyColors(composite);
        return composite;
    }

    /**
     * Creates and returns the contents of the dialog (the area below the title
     * area and above the info text area.
     * <p>
     * The <code>PopupDialog</code> implementation of this framework method
     * creates and returns a new <code>Composite</code> with standard margins
     * and spacing.
     * <p>
     * The returned control's layout data must be an instance of
     * <code>GridData</code>. This method must not modify the parent's layout.
     * <p>
     * Subclasses must override this method but may call <code>super</code> as
     * in the following example:
     * 
     * <pre>
     * Composite composite = (Composite) super.createDialogArea(parent);
     * //add controls to composite as necessary
     * return composite;
     * </pre>
     * 
     * @param parent
     *            the parent composite to contain the dialog area
     * @return the dialog area control
     */
    protected Control createDialogArea(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        POPUP_LAYOUT_FACTORY.applyTo(composite);
        return composite;
    }

    protected boolean hasTitleArea() {
        return titleText != null || showCloseButton;
    }

    protected Control createTitleArea(Composite parent) {
        Composite titleAreaComposite = new Composite(parent, SWT.NONE);

        boolean hasTitle = titleText != null;
        GridLayoutFactory.fillDefaults().numColumns(hasTitle && showCloseButton ? 2 : 1)
                .applyTo(titleAreaComposite);

        if (hasTitle) {
            Control title = createTitleControl(titleAreaComposite);
            LAYOUTDATA_GRAB_HORIZONTAL.applyTo(title);
        }

        if (showCloseButton) {
            Control closeButton = createCloseButton(titleAreaComposite);
            LAYOUTDATA_ALIGN_RIGHT.grab(!hasTitle, false).applyTo(closeButton);
        }

        return titleAreaComposite;
    }

    protected Control createCloseButton(Composite parent) {
        ImageButton closeButton = new ImageButton(parent, SWT.NONE);
        closeButton.setNormalImageDescriptor(getCloseButtonNormalImage());
        closeButton.setDisabledImageDescriptor(getDisabledCloseButtonImage());
        closeButton.setHoveredImageDescriptor(getHoverCloseButtonImage());
        closeButton.setPressedImageDescriptor(getPressedCloseButtonImage());
        closeButton.addOpenListener(new IOpenListener() {
            public void open(OpenEvent event) {
                close();
            }
        });
        return closeButton.getControl();
    }

    public static void setDefaultCloseButtonNormalImage(ImageDescriptor img) {
        IMG_CLOSE_NORMAL = img;
    }

    protected ImageDescriptor getCloseButtonNormalImage() {
        if (IMG_CLOSE_NORMAL == null) {
            IMG_CLOSE_NORMAL = createDefaultCloseButtonImage();
        }
        return IMG_CLOSE_NORMAL;
    }

    private static ImageDescriptor createDefaultCloseButtonImage() {
        Display display = Display.getCurrent();
        Image img = new Image(display, 16, 16);
        GC gc = new GC(img);
        gc.setForeground(display.getSystemColor(SWT.COLOR_GRAY));
        gc.setBackground(ColorUtils.getColor(DEFAULT_BACKGROUDCOLOR_VALUE));
        gc.fillRectangle(0, 0, 16, 16);
        gc.setLineWidth(2);
        gc.drawLine(4, 4, 11, 11);
        gc.drawLine(4, 11, 11, 4);
        gc.dispose();
        ImageData data = img.getImageData();
        img.dispose();
        return ImageDescriptor.createFromImageData(data);
    }

    protected ImageDescriptor getHoverCloseButtonImage() {
        return null;
    }

    protected ImageDescriptor getDisabledCloseButtonImage() {
        return null;
    }

    protected ImageDescriptor getPressedCloseButtonImage() {
        return null;
    }

    protected Control createTitleControl(Composite parent) {
        Composite titleContainer = new Composite(parent, SWT.NONE);
        GridLayoutFactory.fillDefaults().margins(5, 2).spacing(0, 0).numColumns(1).applyTo(titleContainer);

        Label title = new Label(titleContainer, SWT.NONE);
        if (titleText != null)
            title.setText(titleText);
        LAYOUTDATA_GRAB_HORIZONTAL.applyTo(title);

        return titleContainer;
    }

    /**
     * Apply any desired color to the specified composite and its children.
     * 
     * @param composite
     *            the contents composite
     * @param display
     */
    private void applyColors(Composite composite) {
        Display display = getShell().getDisplay();
        applyForegroundColor(display.getSystemColor(SWT.COLOR_DARK_GRAY), composite,
                getForegroundColorExclusions());
        applyBackgroundColor(ColorUtils.getColor(DEFAULT_BACKGROUDCOLOR_VALUE), composite,
                getBackgroundColorExclusions());
    }

    /**
     * Set the specified foreground color for the specified control and all of
     * its children, except for those specified in the list of exclusions.
     * 
     * @param color
     *            the color to use as the foreground color
     * @param control
     *            the control whose color is to be changed
     * @param exclusions
     *            a list of controls who are to be excluded from getting their
     *            color assigned
     */
    private void applyForegroundColor(Color color, Control control, List exclusions) {
        if (exclusions.contains(control))
            return;
        control.setForeground(color);
        if (control instanceof Composite) {
            Control[] children = ((Composite) control).getChildren();
            for (int i = 0; i < children.length; i++) {
                applyForegroundColor(color, children[i], exclusions);
            }
        }
    }

    /**
     * Set the specified background color for the specified control and all of
     * its children.
     * 
     * @param color
     *            the color to use as the background color
     * @param control
     *            the control whose color is to be changed
     * @param exclusions
     *            a list of controls who are to be excluded from getting their
     *            color assigned
     */
    private void applyBackgroundColor(Color color, Control control, List exclusions) {
        if (exclusions.contains(control))
            return;
        control.setBackground(color);
        if (control instanceof Composite) {
            Control[] children = ((Composite) control).getChildren();
            for (int i = 0; i < children.length; i++) {
                applyBackgroundColor(color, children[i], exclusions);
            }
        }
    }

    /**
     * Set the specified foreground color for the specified control and all of
     * its children. Subclasses may override this method, but typically do not.
     * If a subclass wishes to exclude a particular control in its contents from
     * getting the specified foreground color, it may instead override
     * <code>PopupDialog.getForegroundColorExclusions</code>.
     * 
     * @param color
     *            the color to use as the background color
     * @param control
     *            the control whose color is to be changed
     * @see PopupDialog#getBackgroundColorExclusions()
     */
    protected void applyForegroundColor(Color color, Control control) {
        applyForegroundColor(color, control, getForegroundColorExclusions());
    }

    /**
     * Set the specified background color for the specified control and all of
     * its children. Subclasses may override this method, but typically do not.
     * If a subclass wishes to exclude a particular control in its contents from
     * getting the specified background color, it may instead override
     * <code>PopupDialog.getBackgroundColorExclusions</code>.
     * 
     * @param color
     *            the color to use as the background color
     * @param control
     *            the control whose color is to be changed
     * @see PopupDialog#getBackgroundColorExclusions()
     */
    protected void applyBackgroundColor(Color color, Control control) {
        applyBackgroundColor(color, control, getBackgroundColorExclusions());
    }

    /**
     * Return a list of controls which should never have their foreground color
     * reset. Subclasses may extend this method (should always call
     * <code>super.getForegroundColorExclusions</code> to aggregate the list.
     * 
     * @return the List of controls
     */
    protected List getForegroundColorExclusions() {
        List list = new ArrayList(3);
        return list;
    }

    /**
     * Return a list of controls which should never have their background color
     * reset. Subclasses may extend this method (should always call
     * <code>super.getBackgroundColorExclusions</code> to aggregate the list.
     * 
     * @return the List of controls
     */
    protected List getBackgroundColorExclusions() {
        List list = new ArrayList(2);
        return list;
    }

    protected Color getBorderColor(Display display) {
        return null;
    }

    protected Point getTargetSize() {
        return targetSize;
    }

    protected void setTargetSize(Point targetSize) {
        this.targetSize = targetSize;
    }

    protected void setCenterPopUp(boolean centerPopUp) {
        this.centerPopUp = centerPopUp;
    }

    public void setGroupId(String groupId) {
        if (groupId == null) {
            if (group != null) {
                group.remove(this);
                group = null;
            }
        } else {
            group = groups.get(groupId);
            if (group == null) {
                group = new PopupGroup();
                groups.put(groupId, group);
            }
        }
    }

    public void popUp() {
        Display display = Display.getCurrent();

        Shell shell = null;
        if (PlatformUI.isWorkbenchRunning()) {
            IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
            if (window != null) {
                shell = window.getShell();
            }
        }
        if (shell == null)
            shell = display.getActiveShell();
        if (shell != null && !shell.isDisposed()) {
            popUp(shell);
        } else {
            Rectangle area = display.getClientArea();
            popUp(area.x + area.width - 50, area.y + area.height - 50);
        }
    }

    public void popUp(Control sourceControl) {
        open(sourceControl, true);
    }

    public void popUp(int right, int bottom) {
        open(right, bottom, true);
    }

    public void open(Control sourceControl) {
        open(sourceControl, false);
    }

    public void open(int right, int bottom) {
        open(right, bottom, false);
    }

    protected void open(Control sourceControl, boolean popup) {
        if (sourceControlMoveListener != null) {
            sourceControl.removeListener(SWT.Move, sourceControlMoveListener);
            if (this.sourceControl != null) {
                this.sourceControl.removeListener(SWT.Move, sourceControlMoveListener);
            }
        }
        this.sourceControl = sourceControl;
        if (sourceControlMoveListener == null) {
            sourceControlMoveListener = new Listener() {
                public void handleEvent(Event event) {
                    group = null;
                    updateShellBounds(currentHeight);
                }
            };
        }
        this.sourceControl.addListener(SWT.Move, sourceControlMoveListener);

        Point bottomRight = getBottomRight(sourceControl);
        open(bottomRight.x, bottomRight.y, popup);
    }

    private Point getBottomRight(Control sourceControl) {
        Rectangle bounds = getSourceArea(sourceControl);
        Point loc = sourceControl.toDisplay(bounds.x, bounds.y);
        int pointX = loc.x + bounds.width - POPUP_GAP;
        if (centerPopUp) {
            pointX = pointX - bounds.width / 2 + POPUP_GAP;
            if (targetSize != null)
                pointX += targetSize.x / 2;
            else
                pointX += DEFAULT_TARGET_SIZE.x / 2;
        }
        return new Point(pointX, loc.y + bounds.height - POPUP_GAP);
    }

    private Rectangle getSourceArea(Control sourceControl) {

        Rectangle bounds = sourceControl.getBounds();
        if (sourceControl instanceof Composite) {
            Rectangle computedSize = ((Composite) sourceControl).getClientArea();
            return computedSize;
        }
        return bounds;
    }

    protected void open(int right, int bottom, boolean popup) {
        this.startingBottomRight = getStartingBottomRight(right, bottom);
        this.popup = popup;
        open();
    }

    private Point getStartingBottomRight(int right, int bottom) {
        if (group != null) {
            Point bottomRight = group.getBottomRight();
            if (bottomRight == null) {
                group.setBottomRight(right, bottom);
                bottomRight = group.getBottomRight();
            }
            return new Point(bottomRight.x, bottomRight.y);
        }
        return new Point(right, bottom);
    }

    public int open() {
        stop();

        Shell shell = showShell();
        if (group != null) {
            group.add(this, targetHeight, targetWidth);
        }

        if (shell != null && !shell.isDisposed()) {
            if (popup && startingBottomRight != null) {
                doPopUp(shell);
            } else {
                postOpen();
            }
            popup = false;
        }
        return OK;
    }

    private Shell showShell() {
        Shell shell = getShell();
        if (shell == null || shell.isDisposed()) {
            shell = null;
            // create the window
            create();
            shell = getShell();
        }

        initializeBounds();

        // limit the shell size to the display size
        constrainShellSize();

        shell.setVisible(true);
        return shell;
    }

    private void doPopUp(Shell shell) {
        currentHeight = shell.getSize().y;
        timer = new UITimer(0, ANIM_INTERVALS, new SafeRunnable() {
            public void run() {
                currentHeight += VERTICAL_SPEED;
                if (currentHeight > targetHeight) {
                    stop();
                    postOpen();
                } else {
                    updateShellBounds(currentHeight);
                }
            }

        });
        timer.run();
    }

    protected void postOpen() {
        updateShellBounds(targetHeight);
        if (getDuration() > 0) {
            Display display = getShell().getDisplay();
            pullDownTask = new PullDownTask(display);
            display.timerExec(getDuration(), pullDownTask);
        }
        currentHeight = targetHeight;
    }

    protected void updateShellBounds(int height) {
        Shell shell = getShell();
        if (shell != null && !shell.isDisposed()) {
            shell.setRedraw(false);
            shell.setBounds(getCurrentBounds(height, shell));
            shell.setRedraw(true);
        }
    }

    protected Rectangle getCurrentBounds(int height, Shell shell) {
        Point bottomRight;
        Point start;
        if (sourceControl != null && group == null) {
            start = getBottomRight(sourceControl);
        } else {
            start = startingBottomRight;
        }
        if (start != null) {
            bottomRight = new Point(start.x, start.y);
        } else {
            Rectangle bounds = shell.getBounds();
            bottomRight = new Point(bounds.x + bounds.width, bounds.y + bounds.height);
        }
        int x = bottomRight.x - targetWidth;
        int y = bottomRight.y - height;

        return new Rectangle(x, y, targetWidth, height);
    }

    private void stop() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
        if (pullDownTask != null) {
            pullDownTask.cancel();
            pullDownTask = null;
        }
    }

    protected Point getInitialSize() {
        Point size = super.getInitialSize();

        targetWidth = size.x;
        targetHeight = size.y;

        if (!popup)
            return size;

        return new Point(size.x, 1);
    }

    protected Point getInitialLocation(Point initialSize) {
        if (startingBottomRight == null)
            return super.getInitialLocation(initialSize);
        return new Point(startingBottomRight.x - initialSize.x, startingBottomRight.y - initialSize.y);
    }

    public static int getStayDuration() {
        return STAY_DURATION;
    }

    public static void setStayDuration(int duration) {
        STAY_DURATION = duration;
    }

    public boolean isShowing() {
        return getShell() != null && !getShell().isDisposed();
    }

    public boolean close() {
        stop();
        currentHeight = 0;
        if (group != null) {
            group.remove(this);
        }
        return super.close();
    }

    public void pullDown() {
        stop();
        Shell shell = getShell();
        if (shell != null && !shell.isDisposed()) {
            targetHeight = 0;
            doPullDown(shell);
        }
    }

    private void doPullDown(Shell shell) {
        Rectangle bounds = shell.getBounds();
        currentHeight = bounds.height;
        timer = new UITimer(0, ANIM_INTERVALS, new SafeRunnable() {
            public void run() {
                currentHeight -= VERTICAL_SPEED;
                if (currentHeight <= 0) {
                    close();
                } else {
                    updateShellBounds(currentHeight);
                }
            }
        });
        timer.run();
    }

    protected void handleDispose() {
        stop();
        if (sourceControl != null && !sourceControl.isDisposed()) {
            if (sourceControlMoveListener != null) {
                sourceControl.removeListener(SWT.Move, sourceControlMoveListener);
            }
            sourceControl = null;
        }
    }

}