Package org.thechiselgroup.choosel.dnd.client.windows

Source Code of org.thechiselgroup.choosel.dnd.client.windows.WindowPanel

/*******************************************************************************
* Copyright 2009, 2010 Lars Grammel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*    
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. 
*******************************************************************************/
package org.thechiselgroup.choosel.dnd.client.windows;

import java.util.ArrayList;
import java.util.List;

import org.adamtacy.client.ui.NEffectPanel;
import org.adamtacy.client.ui.effects.NEffect;
import org.adamtacy.client.ui.effects.events.EffectCompletedEvent;
import org.adamtacy.client.ui.effects.events.EffectCompletedHandler;
import org.adamtacy.client.ui.effects.impl.Move;
import org.thechiselgroup.choosel.core.client.fx.FXUtil;
import org.thechiselgroup.choosel.core.client.fx.Opacity;
import org.thechiselgroup.choosel.core.client.geometry.Point;
import org.thechiselgroup.choosel.core.client.ui.CSS;
import org.thechiselgroup.choosel.core.client.ui.ResizingTextBox;
import org.thechiselgroup.choosel.core.client.ui.popup.PopupManager;
import org.thechiselgroup.choosel.core.client.ui.popup.PopupManagerFactory;
import org.thechiselgroup.choosel.core.client.util.math.MathUtils;
import org.thechiselgroup.choosel.dnd.client.DragProxyAttachedEvent;
import org.thechiselgroup.choosel.dnd.client.DragProxyAttachedEventHandler;

import com.allen_sauer.gwt.dnd.client.util.WidgetLocation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

public class WindowPanel extends NEffectPanel implements WindowController {

    private static final int INNER_CSS_BORDER_WIDTH = 2;

    private static final int OUTER_CSS_BORDER_WIDTH = 2;

    private static final int BORDER_THICKNESS = 7;

    private static final String CSS_WINDOW = "choosel-WindowPanel";

    private static final String CSS_WINDOW_BUTTON_PANEL = "choosel-WindowPanel-ButtonPanel";

    private static final String CSS_WINDOW_HEADER = "choosel-WindowPanel-Header";

    private static final String CSS_WINDOW_RESIZE_EDGE_LEFT_BORDER = "choosel-WindowPanel-ResizeEdge-LeftBorder";

    private static final String CSS_WINDOW_RESIZE_EDGE_RIGHT_BORDER = "choosel-WindowPanel-ResizeEdge-RightBorder";

    private static final String CSS_WINDOW_RESIZE_EDGE_TOP_BORDER = "choosel-WindowPanel-ResizeEdge-TopBorder";

    private static final String CSS_WINDOW_HEADER_LABEL = "choosel-WindowPanel-HeaderLabel";

    private static final String CSS_WINDOW_HEADER_TEXT = "choosel-WindowPanel-HeaderText";

    private static final String CSS_WINDOW_RESIZE = "choosel-WindowPanel-Resize-";

    private static final String CSS_WINDOW_RESIZE_EDGE = "choosel-WindowPanel-ResizeEdge";

    private static final String IMAGE_CLOSE_ACTIVE = "images/close_active.gif";

    private static final String IMAGE_CLOSE_INVISIBLE = "images/close_invisible.gif";

    private static final String IMAGE_CLOSE_VISIBLE = "images/close_visible.gif";

    private static final int TOTAL_BORDER_THICKNESS = 2 * BORDER_THICKNESS
            + OUTER_CSS_BORDER_WIDTH + INNER_CSS_BORDER_WIDTH;

    private Image closeImage;

    private Widget contentWidget;

    private Widget eastTopWidget;

    private Widget eastWidget;

    private FlexTable grid;

    private Widget headerWidget;

    private Widget northWidget;

    private final List<Widget> removeFromDragControllerOnDispose = new ArrayList<Widget>();

    protected FocusPanel rootPanel;

    private Widget southWidget;

    // TODO move into a better place as this is relevant for persistency only
    private WindowContent windowContent;

    private Widget westTopWidget;

    private Widget westWidget;

    private WindowManager manager;

    private String windowTitle;

    private WindowController controller;

    /**
     * Panel that contains the title of the window and window buttons such as
     * the close button.
     */
    private HorizontalPanel headerBar;

    private PopupManagerFactory popupManagerFactory;

    @Inject
    public WindowPanel(PopupManagerFactory popupManagerFactory) {
        this.popupManagerFactory = popupManagerFactory;
    }

    /**
     * Adjusts the size of the window or the size of its content. If the window
     * is larger than its content, then the content size is increased. If the
     * window is smaller, its size is increased. It also checks for maximum
     * sizes because of desktop limitations.
     */
    public void adjustSize() {
        /*
         * Calculate target width: max of header, adjusted window, content
         *
         * TODO: restrict to available desktop space
         */
        int targetWidth = TOTAL_BORDER_THICKNESS
                + MathUtils.maxInt(headerBar.getOffsetWidth(),
                        contentWidget.getOffsetWidth(), getWidth()
                                - TOTAL_BORDER_THICKNESS);
        /*
         * Calculate target height: max of adjusted window, content
         *
         * TODO: restrict to available desktop space
         */
        int targetHeight = TOTAL_BORDER_THICKNESS
                + MathUtils.maxInt(contentWidget.getOffsetHeight(), getHeight()
                        - TOTAL_BORDER_THICKNESS - headerBar.getOffsetHeight());

        setPixelSize(targetWidth, targetHeight);
    }

    /**
     * Fades out the window and removes it from the window controller
     * afterwards.
     */
    // TODO rename to animate hide, add effect completed handler in method
    public void close() {
        NEffect fade = createHideEffect();

        fade.addEffectCompletedHandler(new EffectCompletedHandler() {
            @Override
            public void onEffectCompleted(EffectCompletedEvent event) {
                removeEffects();
                manager.close(WindowPanel.this);
            }
        });

        addEffect(fade);
        playEffects();
    }

    // hook
    protected ClickHandler createCloseButtonClickHandler() {
        return new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                close();
            }
        };
    }

    // hook
    protected NEffect createHideEffect() {
        return FXUtil.createOpacityMorphEffect(Opacity.OPAQUE,
                Opacity.TRANSPARENT);
    }

    protected NEffect createShowEffect() {
        return FXUtil.createOpacityMorphEffect(Opacity.TRANSPARENT,
                Opacity.OPAQUE);
    }

    // TODO use window manager
    public int getAbsoluteX() {
        return getAbsoluteLeft() - getParent().getAbsoluteLeft();
    }

    // TODO use window manager
    public int getAbsoluteY() {
        return getAbsoluteTop() - getParent().getAbsoluteTop();
    }

    private String getActiveCloseImageUrl() {
        return getModuleBase() + IMAGE_CLOSE_ACTIVE;
    }

    /*
     * hook method
     */
    protected String getClosePopupLabel() {
        return "Close";
    }

    @Override
    public int getHeight() {
        return grid.getOffsetHeight() + OUTER_CSS_BORDER_WIDTH;
    }

    private String getInvisibleCloseImageUrl() {
        return getModuleBase() + IMAGE_CLOSE_INVISIBLE;
    }

    private Point getLocation() {
        return manager.getLocation(this);
    }

    protected String getModuleBase() {
        return GWT.getModuleBaseURL();
    }

    // TODO move into model
    public WindowContent getViewContent() {
        return this.windowContent;
    }

    private String getVisibleCloseImageUrl() {
        return getModuleBase() + IMAGE_CLOSE_VISIBLE;
    }

    @Override
    public int getWidth() {
        return grid.getOffsetWidth() + OUTER_CSS_BORDER_WIDTH;
    }

    public String getWindowTitle() {
        return windowTitle;
    }

    public int getZIndex() {
        return DOM.getIntStyleAttribute(getElement(), CSS.Z_INDEX);
    }

    public void init(WindowManager windowManager, String title,
            boolean titleEditable, Widget contentWidget) {

        this.controller = new DefaultWindowController(new WindowCallback() {
            @Override
            public int getHeight() {
                return WindowPanel.this.getHeight();
            }

            @Override
            public Point getLocation() {
                return WindowPanel.this.getLocation();
            }

            @Override
            public int getWidth() {
                return WindowPanel.this.getWidth();
            }

            @Override
            public void setLocation(int x, int y) {
                WindowPanel.this.setLocation(x, y);
            }

            @Override
            public void setPixelSize(int width, int height) {
                WindowPanel.this.setPixelSize(width, height);
            }
        });

        initShowEvent();

        DOM.setStyleAttribute(getElement(), "border", "0px"); // TODO move to
                                                              // CSS class or
                                                              // CSS file

        this.windowTitle = title;
        this.rootPanel = new FocusPanel();
        setWidget(this.rootPanel);

        this.manager = windowManager;

        rootPanel.addStyleName(CSS_WINDOW);

        this.contentWidget = contentWidget;
        initHeader(windowManager, titleEditable, title);

        rootPanel.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                // XXX fix bug: window not to front
                // deactivated because of list box issue
                // // force our panel to the top of our z-index context
                // AbsolutePanel boundaryPanel = windowController
                // .getBoundaryPanel();
                // WidgetLocation location = new
                // WidgetLocation(WindowPanel.this,
                // boundaryPanel);
                // boundaryPanel.add(WindowPanel.this, location.getLeft(),
                // location.getTop());
            }
        });

        initGrid(contentWidget);
    }

    private Widget initCell(int row, int col, ResizeDirection direction,
            String additionalCSSClass) {

        FocusPanel borderWidget = new FocusPanel();

        /*
         * Sets the size for the elements that are not resized (e.g. corners and
         * corner extensions). The corner extensions have the same size as the
         * corners.
         */
        borderWidget.setPixelSize(BORDER_THICKNESS, BORDER_THICKNESS);

        grid.setWidget(row, col, borderWidget);

        manager.getResizeDragController()
                .makeDraggable(borderWidget, direction);
        removeFromDragControllerOnDispose.add(borderWidget);

        /*
         * all CSS classes need to be set in one call due to limitations in
         * getCellFormatter().addStyleName
         */
        String css = CSS_WINDOW_RESIZE_EDGE;
        css += " " + CSS_WINDOW_RESIZE + direction.getDirectionLetters();
        if (additionalCSSClass != null) {
            css += " " + additionalCSSClass;
        }
        grid.getCellFormatter().addStyleName(row, col, css);

        return borderWidget;
    }

    private void initCloseImage() {
        closeImage = new Image(getInvisibleCloseImageUrl());
        closeImage.addStyleName(CSS_WINDOW_BUTTON_PANEL);

        closeImage.addMouseOverHandler(new MouseOverHandler() {
            @Override
            public void onMouseOver(MouseOverEvent event) {
                closeImage.setUrl(getActiveCloseImageUrl());
            }
        });

        closeImage.addMouseOutHandler(new MouseOutHandler() {
            @Override
            public void onMouseOut(MouseOutEvent event) {
                closeImage.setUrl(getVisibleCloseImageUrl());
            }
        });

        rootPanel.addMouseOverHandler(new MouseOverHandler() {
            @Override
            public void onMouseOver(MouseOverEvent event) {
                closeImage.setUrl(getVisibleCloseImageUrl());
            }
        });

        rootPanel.addMouseOutHandler(new MouseOutHandler() {
            @Override
            public void onMouseOut(MouseOutEvent event) {
                closeImage.setUrl(getInvisibleCloseImageUrl());
            }
        });

        // disable dragging / transparency on mouse down over image
        closeImage.addMouseDownHandler(new MouseDownHandler() {
            @Override
            public void onMouseDown(MouseDownEvent event) {
                event.stopPropagation();
                event.preventDefault();
            }
        });

        closeImage.addClickHandler(createCloseButtonClickHandler());

        // hide close image when dnd operation starts
        addHandler(new DragProxyAttachedEventHandler() {
            @Override
            public void onDragProxyAttached(DragProxyAttachedEvent event) {
                closeImage.setUrl(getInvisibleCloseImageUrl());
            }
        }, DragProxyAttachedEvent.TYPE);

        PopupManager popupManager = popupManagerFactory
                .createPopupManager(new Label(getClosePopupLabel()));
        popupManager.linkToWidget(closeImage);
        popupManager.setHideDelay(0);

        headerBar.add(closeImage);
        headerBar.setCellHorizontalAlignment(closeImage,
                HasAlignment.ALIGN_RIGHT);
        headerBar.setCellVerticalAlignment(closeImage, HasAlignment.ALIGN_TOP);
    }

    private void initGrid(Widget contentWidget) {
        grid = new FlexTable();
        grid.setBorderWidth(0);
        grid.setCellSpacing(0);
        grid.setCellPadding(0);
        rootPanel.add(grid);

        initCell(0, 0, ResizeDirection.NORTH_WEST, null);
        initCell(0, 1, ResizeDirection.NORTH_WEST, null);
        northWidget = initCell(0, 2, ResizeDirection.NORTH, null);
        initCell(0, 3, ResizeDirection.NORTH_EAST, null);
        initCell(0, 4, ResizeDirection.NORTH_EAST, null);

        initCell(1, 0, ResizeDirection.NORTH_WEST, null);
        grid.setWidget(1, 1, headerBar);
        grid.getCellFormatter().addStyleName(1, 1, CSS_WINDOW_HEADER);
        grid.getFlexCellFormatter().setColSpan(1, 1, 3);
        grid.getFlexCellFormatter().setRowSpan(1, 1, 2);
        initCell(1, 2, ResizeDirection.NORTH_EAST, null);

        westTopWidget = initCell(2, 0, ResizeDirection.WEST, null);
        eastTopWidget = initCell(2, 1, ResizeDirection.EAST, null);

        westWidget = initCell(3, 0, ResizeDirection.WEST,
                CSS_WINDOW_RESIZE_EDGE_RIGHT_BORDER);
        grid.setWidget(3, 1, contentWidget);
        grid.getFlexCellFormatter().setColSpan(3, 1, 3);
        grid.getFlexCellFormatter().setRowSpan(3, 1, 2);
        eastWidget = initCell(3, 2, ResizeDirection.EAST,
                CSS_WINDOW_RESIZE_EDGE_LEFT_BORDER);

        initCell(4, 0, ResizeDirection.SOUTH_WEST,
                CSS_WINDOW_RESIZE_EDGE_RIGHT_BORDER);
        initCell(4, 1, ResizeDirection.SOUTH_EAST,
                CSS_WINDOW_RESIZE_EDGE_LEFT_BORDER);

        initCell(5, 0, ResizeDirection.SOUTH_WEST, null);
        initCell(5, 1, ResizeDirection.SOUTH_WEST,
                CSS_WINDOW_RESIZE_EDGE_TOP_BORDER);
        southWidget = initCell(5, 2, ResizeDirection.SOUTH,
                CSS_WINDOW_RESIZE_EDGE_TOP_BORDER);
        initCell(5, 3, ResizeDirection.SOUTH_EAST,
                CSS_WINDOW_RESIZE_EDGE_TOP_BORDER);
        initCell(5, 4, ResizeDirection.SOUTH_EAST, null);
    }

    private void initHeader(WindowManager windowManager, boolean editableTitle,
            String title) {

        headerBar = new HorizontalPanel();
        headerBar.setSize("100%", "");

        initTitleWidget(title, editableTitle, windowManager);
        initMoveLabel(windowManager);
        initCloseImage();
    }

    private void initMoveLabel(WindowManager windowManager) {
        Label moveLabel = new Label(" ");
        moveLabel.getElement().getStyle().setWidth(100d, Unit.PCT);
        moveLabel.getElement().getStyle().setHeight(20, Unit.PX);
        windowManager.getMoveDragController().makeDraggable(this, moveLabel);
        headerBar.add(moveLabel);
        headerBar.setCellWidth(moveLabel, "100%");
    }

    private void initShowEvent() {
        NEffect showEffect = createShowEffect();
        showEffect.addEffectCompletedHandler(new EffectCompletedHandler() {
            @Override
            public void onEffectCompleted(EffectCompletedEvent event) {
                removeEffects();

                // TODO extract constant
                // DOM.setStyleAttribute(rootPanel.getElement(), "opacity",
                // null);
            }
        });
        addEffect(showEffect);
    }

    private void initTitleWidget(String title, boolean editableTitle,
            WindowManager windowManager) {

        if (editableTitle) {
            headerWidget = new ResizingTextBox(20, 250);
            TextBox headerText = (TextBox) this.headerWidget;
            headerText.setText(title);
            headerText.addStyleName(CSS_WINDOW_HEADER_TEXT);
            headerText.addValueChangeHandler(new ValueChangeHandler<String>() {
                @Override
                public void onValueChange(ValueChangeEvent<String> event) {
                    /*
                     * XXX The title is kept at 2 locations right now.
                     */
                    windowContent.setLabel(event.getValue());
                    windowTitle = event.getValue();
                }
            });
        } else {
            headerWidget = new Label(title);
            headerWidget.addStyleName(CSS_WINDOW_HEADER_LABEL);
            windowManager.getMoveDragController().makeDraggable(this,
                    headerWidget);
        }

        headerBar.add(headerWidget);
        headerBar.setCellHorizontalAlignment(headerWidget,
                HasAlignment.ALIGN_LEFT);
    }

    @Override
    protected void onDetach() {
        manager.getMoveDragController().makeNotDraggable(this);

        for (Widget w : removeFromDragControllerOnDispose) {
            manager.getResizeDragController().makeNotDraggable(w);
        }

        // DragController#unregisterDropController

        super.onDetach();
    }

    @Override
    public void onLoad() {
        super.onLoad();

        adjustSize();
        playEffects();
    }

    @Override
    public void resize(int deltaX, int deltaY, int targetWidth, int targetHeight) {
        controller.resize(deltaX, deltaY, targetWidth, targetHeight);
    }

    /**
     * Sets the location of this window on the pane defined by the
     * WindowManager. This version of <code>setLocation</code> is not animated.
     *
     * @param x
     *            x coordinate in the coordinate space defined by the
     *            WindowManager
     * @param y
     *            y coordinate in the coordinate space defined by the
     *            WindowManager
     */
    public void setLocation(int x, int y) {
        manager.setLocation(this, x, y);
    }

    public void setLocation(final int x, final int y, boolean animate) {
        /*
         * Warning: This method is fairly fragile. Proceed with caution.
         */

        if (!animate) {
            setLocation(x, y);
            return;
        }

        assert animate;

        Point location = getLocation();

        Move move = new Move(x - location.getX(), y - location.getY()) {
            @Override
            public void tearDownEffect() {
                /*
                 * do not super.tearDownEffects as this resets to original state
                 * reset root panel position as this is affected by move
                 */
                CSS.setLocation(rootPanel, 0, 0);
            }
        };

        move.addEffectCompletedHandler(new EffectCompletedHandler() {
            @Override
            public void onEffectCompleted(EffectCompletedEvent event) {
                removeEffects();
                setLocation(x, y);
                assert 0 == new WidgetLocation(rootPanel, WindowPanel.this)
                        .getLeft();
                assert 0 == new WidgetLocation(rootPanel, WindowPanel.this)
                        .getTop();
            }
        });

        move.setTransitionType(FXUtil.EASE_OUT);
        move.setDuration(FXUtil.DEFAULT_EFFECT_DURATION);

        addEffect(move);
        playEffects();
    }

    // TODO test
    @Override
    public void setPixelSize(int width, int height) {
        /*
         * setPixelSize calculates the expected size of the content widget and
         * sets it to this size. It then requests the real size of the content
         * widget, because we have no better way to find out about the minimum
         * size. Afterwards, we change the border width and finally adjust the
         * window size.
         *
         * This method breaks easily, so proceed with caution here.
         */
        assert width >= 0;
        assert height >= 0;

        if (width == getWidth() && height == getHeight()) {
            return;
        }

        int headerHeight = headerBar.getOffsetHeight();

        /*
         * Restrict the width/height to be at least the minimum determined by
         * header & borders.
         */
        width = Math.max(width, 4 * TOTAL_BORDER_THICKNESS);
        height = Math.max(height, 4 * TOTAL_BORDER_THICKNESS + headerHeight);

        int contentWidth = width - TOTAL_BORDER_THICKNESS;
        int contentHeight = height - TOTAL_BORDER_THICKNESS - headerHeight;

        contentWidget.setPixelSize(contentWidth, contentHeight);

        int realContentWidth = contentWidget.getOffsetWidth();
        int realContentHeight = contentWidget.getOffsetHeight();

        headerBar.setPixelSize(realContentWidth, headerHeight);

        /*
         * adjust for the case where headerWidth > contentContentWidth
         * (otherwise content does not fill the available space)
         */
        int headerWidth = headerBar.getOffsetWidth();
        if (headerWidth > realContentWidth) {
            realContentWidth = headerWidth;
        }

        /*
         * If the real size is different from the set content size, we set it
         * again. This enables the content widget to set the right sizes in
         * affected child widgets.
         */
        if (contentHeight != realContentHeight
                || contentWidth != realContentWidth) {
            contentWidget.setPixelSize(realContentWidth, realContentHeight);
        }

        updateBorderWidths(realContentWidth, realContentHeight, headerHeight);

        int windowWidth = realContentWidth + TOTAL_BORDER_THICKNESS;
        int windowHeight = realContentHeight + TOTAL_BORDER_THICKNESS
                + headerHeight;

        super.setPixelSize(windowWidth, windowHeight);
    }

    public void setViewContent(WindowContent viewContent) {
        this.windowContent = viewContent;
    }

    public void setZIndex(final int zIndex) {
        // Bugfix: need to set zIndex manually because of timeline/firefox issue
        CSS.setZIndex(this, zIndex);
    }

    private void updateBorderWidths(int contentWidth, int contentHeight,
            int headerHeight) {

        northWidget.setPixelSize(contentWidth - 2 * BORDER_THICKNESS,
                BORDER_THICKNESS);
        southWidget.setPixelSize(contentWidth - 2 * BORDER_THICKNESS,
                BORDER_THICKNESS);

        westTopWidget.setPixelSize(BORDER_THICKNESS, headerHeight
                - BORDER_THICKNESS);
        eastTopWidget.setPixelSize(BORDER_THICKNESS, headerHeight
                - BORDER_THICKNESS);

        westWidget.setPixelSize(BORDER_THICKNESS, contentHeight
                - BORDER_THICKNESS);
        eastWidget.setPixelSize(BORDER_THICKNESS, contentHeight
                - BORDER_THICKNESS);
    }
}
TOP

Related Classes of org.thechiselgroup.choosel.dnd.client.windows.WindowPanel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.