org.eclipse.che.ide.toolbar.PopupMenu.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.ide.toolbar.PopupMenu.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2015 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.ide.toolbar;

import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.ActionGroup;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.Presentation;
import org.eclipse.che.ide.api.action.Separator;
import org.eclipse.che.ide.api.action.ToggleAction;
import org.eclipse.che.ide.api.keybinding.KeyBindingAgent;
import org.eclipse.che.ide.collections.Array;
import org.eclipse.che.ide.collections.Collections;
import org.eclipse.che.ide.util.input.KeyMapUtil;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;

import org.vectomatic.dom.svg.ui.SVGImage;
import org.vectomatic.dom.svg.ui.SVGResource;

/**
 * PopupMenu is visual component represents all known Popup Menu.
 *
 * @author <a href="mailto:gavrikvetal@gmail.com">Vitaliy Gulyy</a>
 */

public class PopupMenu extends Composite {

    private static final PopupResources POPUP_RESOURCES = GWT.create(PopupResources.class);

    static {
        POPUP_RESOURCES.popup().ensureInjected();
    }

    private final ActionGroup actionGroup;
    private final ActionManager actionManager;
    private final String place;
    /** Working variable is needs to indicate when PopupMenu has at list one MenuItem with selected state. */
    private boolean hasCheckedItems;
    /** Callback uses for notify Parent Menu when menu item is selecred. */
    private ActionSelectedHandler actionSelectedHandler;
    /**
     * Lock layer uses as root for displaying this PopupMenu and uses for locking screen and hiding menu when user just clicked outside
     * menu.
     */
    private MenuLockLayer lockLayer;

    /** Contains opened sub Popup Menu. */
    private PopupMenu openedSubPopup;
    private Element subPopupAnchor;

    /** Contains HTML element ( <TR> ) which is hovered for the current time. */
    private Element hoveredTR;

    /**
     * Working variable.
     * PopupMenu panel.
     */
    private SimplePanel popupMenuPanel;
    /** Working variable. Special table uses for handling mouse events. */
    private PopupMenuTable table;
    private PresentationFactory presentationFactory;
    private KeyBindingAgent keyBindingAgent;
    /**
     * Prefix to be appended to the ID for each menu item.
     * This is debug feature.
     */
    private String itemIdPrefix;
    private Array<Action> list;

    private Timer openSubPopupTimer = new Timer() {
        @Override
        public void run() {
            openSubPopup(hoveredTR);
        }
    };

    private Timer closeSubPopupTimer = new Timer() {
        @Override
        public void run() {
            if (openedSubPopup != null) {
                openedSubPopup.closePopup();
                openedSubPopup = null;

                Element e = subPopupAnchor;
                subPopupAnchor = null;
                setStyleNormal(e);
            }
        }
    };

    /**
     * Create PopupMenu
     *
     * @param lockLayer
     *         - lock layer, uses as rot for attaching this popup menu.
     * @param actionSelectedHandler
     *         - callback, uses for notifying parent menu when menu item is selected.
     */
    public PopupMenu(ActionGroup actionGroup, ActionManager actionManager, String place,
            PresentationFactory presentationFactory, MenuLockLayer lockLayer,
            ActionSelectedHandler actionSelectedHandler, KeyBindingAgent keyBindingAgent, String itemIdPrefix) {
        this.actionGroup = actionGroup;
        this.actionManager = actionManager;
        this.place = place;
        this.presentationFactory = presentationFactory;
        this.keyBindingAgent = keyBindingAgent;
        this.itemIdPrefix = itemIdPrefix;

        list = Collections.createArray();
        Utils.expandActionGroup(actionGroup, list, presentationFactory, place, actionManager);

        this.lockLayer = lockLayer;
        this.actionSelectedHandler = actionSelectedHandler;

        popupMenuPanel = new SimplePanel();
        initWidget(popupMenuPanel);

        popupMenuPanel.addDomHandler(new MouseOutHandler() {
            @Override
            public void onMouseOut(MouseOutEvent event) {
                closeSubPopupTimer.cancel();

                PopupMenu.this.setStyleNormal(hoveredTR);
                hoveredTR = null;

                if (subPopupAnchor != null) {
                    setStyleHovered(subPopupAnchor);
                }
            }
        }, MouseOutEvent.getType());

        popupMenuPanel.setStyleName(POPUP_RESOURCES.popup().popupMenuMain());

        hasCheckedItems = hasCheckedItems();

        redraw();
    }

    private boolean isRowEnabled(Element tr) {
        if (tr == null) {
            return false;
        }

        String index = tr.getAttribute("item-index");
        if (index == null || "".equals(index)) {
            return false;
        }

        String enabled = tr.getAttribute("item-enabled");
        if (enabled == null || "".equals(enabled) || "false".equals(enabled)) {
            return false;
        }

        int itemIndex = Integer.parseInt(index);
        Action menuItem = list.get(itemIndex);
        return presentationFactory.getPresentation(menuItem).isEnabled();

    }

    /** Close this Popup Menu. */
    public void closePopup() {
        if (openedSubPopup != null) {
            openedSubPopup.closePopup();
        }

        removeFromParent();
    }

    /** Render Popup Menu component. */
    private void redraw() {
        String idPrefix = itemIdPrefix;
        if (idPrefix == null) {
            idPrefix = "";
        } else {
            idPrefix += "/";
        }

        table = new PopupMenuTable();
        table.setStyleName(POPUP_RESOURCES.popup().popupMenuTable());
        table.setCellPadding(0);
        table.setCellSpacing(0);
        table.getElement().setAttribute("border", "0");

        for (int i = 0; i < list.size(); i++) {
            Action menuItem = list.get(i);

            if (menuItem instanceof Separator) {
                if (i > 0 && i < list.size() - 1) {
                    table.getFlexCellFormatter().setColSpan(i, 0, hasCheckedItems ? 5 : 4);
                    table.setHTML(i, 0, "<nobr><hr noshade=\"noshade\" size=\"1\"></nobr>");
                    table.getCellFormatter().setStyleName(i, 0, POPUP_RESOURCES.popup().popupMenuDelimiter());
                }
            } else {
                Presentation presentation = presentationFactory.getPresentation(menuItem);

                if (presentation.getSVGIcon() != null) {
                    SVGImage image = new SVGImage(presentation.getSVGIcon());
                    image.getElement().getStyle().setMarginTop(2, Unit.PX);
                    table.setWidget(i, 0, image);
                } else {
                    Image image = null;
                    if (presentation.getIcon() != null) {
                        image = new Image(presentation.getIcon());
                    }
                    table.setWidget(i, 0, image);
                }
                table.getCellFormatter().setStyleName(i, 0,
                        presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuIconField()
                                : POPUP_RESOURCES.popup().popupMenuIconFieldDisabled());

                int work = 1;

                if (hasCheckedItems && menuItem instanceof ToggleAction) {
                    String checkImage;
                    ToggleAction toggleAction = (ToggleAction) menuItem;
                    ActionEvent e = new ActionEvent(place, presentationFactory.getPresentation(toggleAction),
                            actionManager, 0);
                    if (toggleAction.isSelected(e)) {
                        table.setHTML(i, work, AbstractImagePrototype.create(POPUP_RESOURCES.check()).getHTML());
                    }
                    table.getCellFormatter().setStyleName(i, work,
                            presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuCheckField()
                                    : POPUP_RESOURCES.popup().popupMenuCheckFieldDisabled());
                    work++;

                }

                table.setHTML(i, work, "<nobr id=\"" + idPrefix + presentation.getText() + "\">"
                        + presentation.getText() + "</nobr>");
                table.getCellFormatter().setStyleName(i, work,
                        presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuTitleField()
                                : POPUP_RESOURCES.popup().popupMenuTitleFieldDisabled());

                work++;
                String hotKey = KeyMapUtil
                        .getShortcutText(keyBindingAgent.getKeyBinding(actionManager.getId(menuItem)));
                if (hotKey == null) {
                    hotKey = "&nbsp;";
                } else {
                    hotKey = "<nobr>&nbsp;" + hotKey + "&nbsp;</nobr>";
                }

                table.setHTML(i, work, hotKey);
                table.getCellFormatter().setStyleName(i, work,
                        presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuHotKeyField()
                                : POPUP_RESOURCES.popup().popupMenuHotKeyFieldDisabled());

                work++;

                if (menuItem instanceof ActionGroup && !(((ActionGroup) menuItem).canBePerformed() && !Utils
                        .hasVisibleChildren((ActionGroup) menuItem, presentationFactory, actionManager, place))) {
                    table.setWidget(i, work, new SVGImage(POPUP_RESOURCES.subMenu()));
                    table.getCellFormatter().setStyleName(i, work,
                            presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuSubMenuField()
                                    : POPUP_RESOURCES.popup().popupMenuSubMenuFieldDisabled());
                } else {
                    table.getCellFormatter().setStyleName(i, work,
                            presentation.isEnabled() ? POPUP_RESOURCES.popup().popupMenuSubMenuField()
                                    : POPUP_RESOURCES.popup().popupMenuSubMenuFieldDisabled());
                }

                work++;

                table.getRowFormatter().getElement(i).setAttribute("item-index", Integer.toString(i));
                table.getRowFormatter().getElement(i).setAttribute("item-enabled",
                        Boolean.toString(presentation.isEnabled()));

                String actionId = actionManager.getId(menuItem);
                String debugId;
                if (actionId == null) {
                    debugId = idPrefix + menuItem.getTemplatePresentation().getText();
                } else {
                    debugId = idPrefix + actionId;
                }
                UIObject.ensureDebugId(table.getRowFormatter().getElement(i), debugId);
            }
        }

        popupMenuPanel.add(table);
    }

    /** @return true when at list one item from list of menu items has selected state. */
    private boolean hasCheckedItems() {
        for (int i = 0; i < list.size(); i++) {
            Action action = list.get(i);
            if (action instanceof ToggleAction) {

                ActionEvent e = new ActionEvent(place, presentationFactory.getPresentation(action), actionManager,
                        0);
                if (((ToggleAction) action).isSelected(e)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Handling MouseOut event.
     *
     * @param row
     *         - element to be processed.
     */
    protected void setStyleNormal(Element row) {
        if (row == null) {
            return;
        }

        if (hasCheckedItems) {
            Element iconTD = DOM.getChild(row, 0);
            Element checkTD = DOM.getChild(row, 1);
            Element titleTD = DOM.getChild(row, 2);
            Element hotKeyTD = DOM.getChild(row, 3);
            Element submenuTD = DOM.getChild(row, 4);

            iconTD.setClassName(POPUP_RESOURCES.popup().popupMenuIconField());
            checkTD.setClassName(POPUP_RESOURCES.popup().popupMenuCheckField());
            titleTD.setClassName(POPUP_RESOURCES.popup().popupMenuTitleField());
            hotKeyTD.setClassName(POPUP_RESOURCES.popup().popupMenuHotKeyField());
            submenuTD.setClassName(POPUP_RESOURCES.popup().popupMenuSubMenuField());
        } else {
            Element iconTD = DOM.getChild(row, 0);
            Element titleTD = DOM.getChild(row, 1);
            Element hotKeyTD = DOM.getChild(row, 2);
            Element submenuTD = DOM.getChild(row, 3);

            iconTD.setClassName(POPUP_RESOURCES.popup().popupMenuIconField());
            titleTD.setClassName(POPUP_RESOURCES.popup().popupMenuTitleField());
            hotKeyTD.setClassName(POPUP_RESOURCES.popup().popupMenuHotKeyField());
            submenuTD.setClassName(POPUP_RESOURCES.popup().popupMenuSubMenuField());
        }
    }

    private void setStyleHovered(Element tr) {
        if (hasCheckedItems) {
            Element iconTD = DOM.getChild(tr, 0);
            Element checkTD = DOM.getChild(tr, 1);
            Element titleTD = DOM.getChild(tr, 2);
            Element hotKeyTD = DOM.getChild(tr, 3);
            Element submenuTD = DOM.getChild(tr, 4);

            iconTD.setClassName(POPUP_RESOURCES.popup().popupMenuIconFieldOver());
            checkTD.setClassName(POPUP_RESOURCES.popup().popupMenuCheckFieldOver());
            titleTD.setClassName(POPUP_RESOURCES.popup().popupMenuTitleFieldOver());
            hotKeyTD.setClassName(POPUP_RESOURCES.popup().popupMenuHotKeyFieldOver());
            submenuTD.setClassName(POPUP_RESOURCES.popup().popupMenuSubMenuFieldOver());
        } else {
            Element iconTD = DOM.getChild(tr, 0);
            Element titleTD = DOM.getChild(tr, 1);
            Element hotKeyTD = DOM.getChild(tr, 2);
            Element submenuTD = DOM.getChild(tr, 3);

            iconTD.setClassName(POPUP_RESOURCES.popup().popupMenuIconFieldOver());
            titleTD.setClassName(POPUP_RESOURCES.popup().popupMenuTitleFieldOver());
            hotKeyTD.setClassName(POPUP_RESOURCES.popup().popupMenuHotKeyFieldOver());
            submenuTD.setClassName(POPUP_RESOURCES.popup().popupMenuSubMenuFieldOver());
        }
    }

    /**
     * Handling MouseOver event.
     *
     * @param tr
     *         - element to be processed.
     */
    protected void onRowHovered(Element tr) {
        if (tr == hoveredTR) {
            return;
        }

        setStyleNormal(hoveredTR);
        if (subPopupAnchor != null) {
            setStyleHovered(subPopupAnchor);
        }

        if (!isRowEnabled(tr)) {
            hoveredTR = null;
            return;
        }

        hoveredTR = tr;
        setStyleHovered(tr);

        int itemIndex = Integer.parseInt(tr.getAttribute("item-index"));
        Action menuItem = list.get(itemIndex);
        openSubPopupTimer.cancel();
        if (menuItem instanceof ActionGroup && !(((ActionGroup) menuItem).canBePerformed()
                && !Utils.hasVisibleChildren((ActionGroup) menuItem, presentationFactory, actionManager, place))) {
            openSubPopupTimer.schedule(300);
        } else {
            closeSubPopupTimer.cancel();
            closeSubPopupTimer.schedule(200);
        }
    }

    /**
     * Handle Mouse Click
     *
     * @param tr
     */
    protected void onRowClicked(Element tr) {
        if (!isRowEnabled(tr) || tr == subPopupAnchor) {
            return;
        }

        int itemIndex = Integer.parseInt(tr.getAttribute("item-index"));
        Action menuItem = list.get(itemIndex);
        if (menuItem instanceof ActionGroup && (!((ActionGroup) menuItem).canBePerformed()
                && Utils.hasVisibleChildren((ActionGroup) menuItem, presentationFactory, actionManager, place))) {
            openSubPopup(tr);
        } else {
            if (actionSelectedHandler != null) {
                actionSelectedHandler.onActionSelected(menuItem);
            }
            ActionEvent e = new ActionEvent(place, presentationFactory.getPresentation(menuItem), actionManager, 0);
            menuItem.actionPerformed(e);
        }
    }

    private void openSubPopup(final Element tableRowElement) {
        if (tableRowElement == null) {
            return;
        }

        if (openedSubPopup != null) {
            if (tableRowElement == subPopupAnchor) {
                return;
            }

            openedSubPopup.closePopup();
        }

        if (subPopupAnchor != null) {
            Element e = subPopupAnchor;
            subPopupAnchor = null;
            setStyleNormal(e);
        }

        subPopupAnchor = tableRowElement;
        setStyleHovered(subPopupAnchor);

        int itemIndex = Integer.parseInt(tableRowElement.getAttribute("item-index"));
        Action menuItem = list.get(itemIndex);

        String idPrefix = itemIdPrefix;
        if (idPrefix != null) {
            idPrefix += "/" + presentationFactory.getPresentation(menuItem).getText();
        }

        openedSubPopup = new PopupMenu((ActionGroup) menuItem, actionManager, place, presentationFactory, lockLayer,
                actionSelectedHandler, keyBindingAgent, idPrefix);

        final int HORIZONTAL_OFFSET = 3;
        final int VERTICAL_OFFSET = 1;

        openedSubPopup.getElement().getStyle().setVisibility(Visibility.HIDDEN);
        lockLayer.add(openedSubPopup, 0, 0);

        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                int left = getAbsoluteLeft() + getOffsetWidth() - HORIZONTAL_OFFSET;
                int top = tableRowElement.getAbsoluteTop() - lockLayer.getTopOffset() - VERTICAL_OFFSET;

                if (left + openedSubPopup.getOffsetWidth() > Window.getClientWidth()) {
                    if (left > openedSubPopup.getOffsetWidth()) {
                        left = getAbsoluteLeft() - openedSubPopup.getOffsetWidth() + HORIZONTAL_OFFSET;
                    } else {
                        int diff = left + openedSubPopup.getOffsetWidth() - Window.getClientWidth();
                        left -= diff;
                    }
                }

                if (top + openedSubPopup.getOffsetHeight() > Window.getClientHeight()) {
                    if (top > openedSubPopup.getOffsetHeight()) {
                        top = tableRowElement.getAbsoluteTop() - openedSubPopup.getOffsetHeight() + VERTICAL_OFFSET;
                    } else {
                        int diff = top + openedSubPopup.getOffsetHeight() - Window.getClientHeight();
                        top -= diff;
                    }
                }

                openedSubPopup.getElement().getStyle().setLeft(left, Unit.PX);
                openedSubPopup.getElement().getStyle().setTop(top, Unit.PX);
                openedSubPopup.getElement().getStyle().setVisibility(Visibility.VISIBLE);
            }
        });
    }

    interface PopupResources extends ClientBundle {

        @Source({ "popup-menu.css", "org/eclipse/che/ide/api/ui/style.css" })
        Css popup();

        @Source("check.gif")
        ImageResource check();

        @Source("org/eclipse/che/ide/menu/submenu.svg")
        SVGResource subMenu();
    }

    interface Css extends CssResource {

        String popupMenuSubMenuFieldDisabled();

        String popupMenuHotKeyFieldDisabled();

        String popupMenuTitleField();

        String popupMenuIconField();

        String popupMenuDelimiter();

        String popupMenuIconFieldDisabled();

        String popupMenuIconFieldOver();

        String popupMenuCheckFieldOver();

        String popupMenuCheckField();

        String popupMenuTable();

        String popupMenuSubMenuField();

        String popupMenuMain();

        String popupMenuTitleFieldOver();

        String popupMenuTitleFieldDisabled();

        String popupMenuCheckFieldDisabled();

        String popupMenuHotKeyFieldOver();

        String popupMenuSubMenuFieldOver();

        String popupMenuHotKeyField();
    }

    /** This table uses for handling mouse events. */
    private class PopupMenuTable extends FlexTable {

        public PopupMenuTable() {
            sinkEvents(Event.ONMOUSEOVER | Event.ONCLICK);
        }

        @Override
        public void onBrowserEvent(Event event) {
            Element td = getEventTargetCell(event);

            if (td == null) {
                return;
            }
            Element tr = DOM.getParent(td);

            String index = tr.getAttribute("item-index");
            if (index == null || "".equals(index)) {
                return;
            }

            switch (DOM.eventGetType(event)) {
            case Event.ONMOUSEOVER:
                onRowHovered(tr);
                break;

            case Event.ONCLICK:
                onRowClicked(tr);
                event.preventDefault();
                event.stopPropagation();
                break;
            }

        }
    }
}