org.waveprotocol.wave.client.wavepanel.impl.edit.BlipMenuController.java Source code

Java tutorial

Introduction

Here is the source code for org.waveprotocol.wave.client.wavepanel.impl.edit.BlipMenuController.java

Source

/**
 * 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.waveprotocol.wave.client.wavepanel.impl.edit;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;

import org.waveprotocol.wave.client.common.util.KeyComboContext;
import org.waveprotocol.wave.client.common.util.KeyComboManager;
import org.waveprotocol.wave.client.common.util.KeyComboTask;
import org.waveprotocol.wave.client.wavepanel.WavePanel;
import org.waveprotocol.wave.client.wavepanel.event.EventDispatcherPanel;
import org.waveprotocol.wave.client.wavepanel.event.EventHandlerRegistry;
import org.waveprotocol.wave.client.wavepanel.event.MouseComeLeaveEvent;
import org.waveprotocol.wave.client.wavepanel.event.WaveClickHandler;
import org.waveprotocol.wave.client.wavepanel.event.WaveContextMenuHandler;
import org.waveprotocol.wave.client.wavepanel.event.WaveDoubleClickHandler;
import org.waveprotocol.wave.client.wavepanel.event.WaveMouseComeLeaveHandler;
import org.waveprotocol.wave.client.wavepanel.view.BlipView;
import org.waveprotocol.wave.client.wavepanel.view.View.Type;
import org.waveprotocol.wave.client.wavepanel.view.dom.DomAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.DomUtil;
import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.TypeCodes;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.i18n.BlipMessages;
import org.waveprotocol.wave.client.widget.menu.PopupMenu;
import org.waveprotocol.wave.client.widget.popup.RelativePopupPositioner;
import org.waveprotocol.wave.model.conversation.ConversationBlip;
import org.waveprotocol.wave.model.conversation.ConversationThread;
import org.waveprotocol.wave.model.conversation.navigator.ConversationNavigator;

/**
 * Controller of the blip's menu.
 */
public class BlipMenuController
        implements WaveMouseComeLeaveHandler, WaveClickHandler, WaveDoubleClickHandler, WaveContextMenuHandler {

    /** Menu showing delay in ms. */
    private final static int SHOW_MENU_DELAY_MS = 100;

    private static final BlipMessages messages = GWT.create(BlipMessages.class);

    /**
     * Creates and installs the blipView popupMenu feature.
     */
    public static void install(WavePanel panel, ModelAsViewProvider modelProvider, Actions actions,
            ConversationNavigator navigator) {
        BlipMenuController controller = new BlipMenuController(actions, panel, modelProvider, navigator);
        EventHandlerRegistry handlers = panel.getHandlers();
        handlers.registerMouseComeLeaveHandler(TypeCodes.kind(Type.BLIP_TIME), controller);
        handlers.registerMouseComeLeaveHandler(TypeCodes.kind(Type.BLIP_MENU_BUTTON), controller);
        handlers.registerClickHandler(TypeCodes.kind(Type.BLIP_TIME), controller);
        handlers.registerClickHandler(TypeCodes.kind(Type.BLIP_MENU_BUTTON), controller);
        handlers.registerDoubleClickHandler(TypeCodes.kind(Type.BLIP), controller);
    }

    private final Actions actions;
    private final DomAsViewProvider viewProvider;
    private final ModelAsViewProvider modelProvider;
    private final ConversationNavigator navigator;

    private PopupMenu popupMenu;
    private Element menuContextElement;
    private NativeEvent menuContextEvent;

    private final Timer showTimer = new Timer() {

        @Override
        public void run() {
            showPopupMenu();
        }
    };

    private boolean editSessionStarted;

    // Walkaround of GWT's bug (when popup is too narrow at the first appearance).
    private boolean inited = false;

    BlipMenuController(Actions actions, WavePanel panel, ModelAsViewProvider modelProvider,
            ConversationNavigator navigator) {
        this.actions = actions;
        this.viewProvider = panel.getViewProvider();
        this.modelProvider = modelProvider;
        this.navigator = navigator;
    }

    @Override
    public boolean onClick(ClickEvent event, Element element) {
        if (DomUtil.isQuasiDeleted(element)) {
            return false;
        }

        if (!isEnabled()) {
            return false;
        }
        menuContextElement = getMenuContext(element);
        menuContextEvent = event.getNativeEvent();
        hideAndScheduleShowPopupMenu();
        event.stopPropagation();
        event.preventDefault();
        return true;
    }

    @Override
    public boolean onMouseComeLeave(MouseComeLeaveEvent event, Element element) {
        // Ignore deleted blip.
        if (DomUtil.isQuasiDeleted(element)) {
            return false;
        }

        // Ignore mouse moving between 'bliptime' and 'blipmenu' elements.
        Element thisMenuContext = getMenuContext(element);
        Element anotherMenuContext = getMenuContext(event.getAnotherElement());
        if (thisMenuContext == anotherMenuContext) {
            return false;
        }

        // Ignore mouse coming with pressed left button.
        if (event.isCome() && EventDispatcherPanel.areMouseButtonsPressed(NativeEvent.BUTTON_LEFT)) {
            return false;
        }

        menuContextElement = thisMenuContext;
        menuContextEvent = event.getNativeEvent();

        if (event.isCome() && isEnabled()) {
            hideAndScheduleShowPopupMenu();
        } else {
            hidePopupMenu();
        }
        return true;
    }

    @Override
    public boolean onContextMenu(ContextMenuEvent event, Element context) {
        // Ignore quasi-deleted or disabled blip.
        if (DomUtil.isQuasiDeleted(context) || !isEnabled()) {
            return false;
        }

        menuContextElement = context;
        menuContextEvent = event.getNativeEvent();
        hideAndScheduleShowPopupMenu();
        event.stopPropagation();
        event.preventDefault();
        return true;
    }

    @Override
    public boolean onDoubleClick(DoubleClickEvent event, Element context) {
        // Ignore empty or disabled blip.
        if (DomUtil.findFirstChildElement(context, Type.META) == null || !isEnabled()) {
            return false;
        }

        // Ignore deleted blip.
        if (DomUtil.isQuasiDeleted(context)) {
            return true;
        }

        menuContextElement = context;
        menuContextEvent = event.getNativeEvent();
        hideAndScheduleShowPopupMenu();
        event.stopPropagation();
        event.preventDefault();
        return true;
    }

    private Element getMenuContext(Element e) {
        if (DomUtil.doesElementHaveType(e, Type.BLIP_MENU_BUTTON)) {
            return e;
        }
        if (DomUtil.doesElementHaveType(e, Type.BLIP_TIME)) {
            return DomUtil.findFirstSiblingElement(e, Type.BLIP_MENU_BUTTON);
        }
        return null;
    }

    private void hideAndScheduleShowPopupMenu() {
        hidePopupMenu();
        showTimer.schedule(SHOW_MENU_DELAY_MS);
    }

    private void hidePopupMenu() {
        showTimer.cancel();
        if (popupMenu != null) {
            popupMenu.hide();
            popupMenu = null;
        }
    }

    private void showPopupMenu() {
        if (popupMenu == null) {
            popupMenu = createPopupMenu();
            popupMenu.show();
        }
    }

    private PopupMenu createPopupMenu() {
        final PopupMenu menu = new PopupMenu(menuContextElement, new RelativePopupPositioner() {

            @Override
            public void setPopupPositionAndMakeVisible(Element relative, Element popup) {
                int left, top;
                if (DomUtil.doesElementHaveType(menuContextElement, Type.BLIP_MENU_BUTTON)) {
                    int popupWidth = popup.getOffsetWidth();
                    left = relative.getAbsoluteRight() - popupWidth;
                    top = relative.getAbsoluteBottom();
                } else {
                    left = menuContextEvent.getClientX();
                    top = menuContextEvent.getClientY();
                }
                Style popupStyle = popup.getStyle();
                popupStyle.setLeft(left, Style.Unit.PX);
                popupStyle.setTop(top, Style.Unit.PX);
                popupStyle.setPosition(Style.Position.FIXED);
                popupStyle.setVisibility(Style.Visibility.VISIBLE);
            }
        });

        BlipView blipView = getBlip();
        final ConversationBlip blip = modelProvider.getBlip(blipView);
        boolean beingEdited = blipView.isBeingEdited();
        boolean focused = blipView.isFocused();

        // "Edit" popup menu item.
        if (!beingEdited) {
            menu.addItem(
                    messages.edit(), ((focused && inited) ? KeyComboManager
                            .getFirstKeyComboHintByTask(KeyComboContext.WAVE, KeyComboTask.EDIT_BLIP) : null),
                    new Command() {

                        @Override
                        public void execute() {
                            actions.startEditing(blip);
                            menu.hide();
                        }
                    }, true);
        }

        // "Add" popup menu item.
        if (!beingEdited) {
            String text = navigator.isBlipLastInParentThread(blip) ? messages.add() : messages.insert();
            menu.addItem(text, ((focused && inited) ? KeyComboManager
                    .getFirstKeyComboHintByTask(KeyComboContext.TEXT_EDITOR, KeyComboTask.CANCEL_EDITING) : null),
                    new Command() {

                        @Override
                        public void execute() {
                            actions.addBlipAfter(blip);
                            menu.hide();
                        }
                    }, true);
        }

        // "Reply" popup menu item.
        if (!beingEdited) {
            menu.addItem(
                    messages.reply(), ((focused && inited) ? KeyComboManager
                            .getFirstKeyComboHintByTask(KeyComboContext.WAVE, KeyComboTask.REPLY_TO_BLIP) : null),
                    new Command() {

                        @Override
                        public void execute() {
                            actions.reply(blip);
                            menu.hide();
                        }
                    }, true);
        }

        // "Done" popup menu item.
        if (beingEdited) {
            menu.addItem(messages.done(),
                    ((focused && inited) ? KeyComboManager.getFirstKeyComboHintByTask(KeyComboContext.TEXT_EDITOR,
                            KeyComboTask.DONE_WITH_EDITING) : null),
                    new Command() {

                        @Override
                        public void execute() {
                            actions.stopEditing(true);
                            menu.hide();
                        }
                    }, true);
        }

        // "Delete" popup menu item.
        menu.addItem(messages.delete(),
                ((focused && inited)
                        ? KeyComboManager.getFirstKeyComboHintByTask(KeyComboContext.WAVE, KeyComboTask.DELETE_BLIP)
                        : null),
                new Command() {

                    @Override
                    public void execute() {
                        actions.deleteBlip(blip, Actions.DeleteOption.WITH_CONFIRMATION);
                        menu.hide();
                    }
                }, true);

        // "Delete thread" popup menu item.
        ConversationThread thread = blip.getThread();
        if (!thread.isRoot() && thread.getFirstBlip() == blip && navigator.getChildBlipCount(thread) > 1) {
            menu.addItem(messages.deleteThread(), null, new Command() {

                @Override
                public void execute() {
                    actions.deleteParentThread(blip, Actions.DeleteOption.WITH_CONFIRMATION);
                    menu.hide();
                }
            }, true);
        }

        // "Link" popup menu item.
        if (!beingEdited) {
            menu.addItem(
                    messages.link(), ((focused && inited) ? KeyComboManager
                            .getFirstKeyComboHintByTask(KeyComboContext.WAVE, KeyComboTask.POPUP_LINK) : null),
                    new Command() {

                        @Override
                        public void execute() {
                            actions.popupLink(blip);
                            menu.hide();
                        }
                    }, true);
        }

        inited = true;

        return menu;
    }

    private BlipView getBlip() {
        BlipView blipView;
        if (DomUtil.doesElementHaveType(menuContextElement, Type.BLIP_MENU_BUTTON)) {
            blipView = getBlipByMenuButton(menuContextElement);
        } else {
            blipView = viewProvider.asBlip(menuContextElement);
        }
        return blipView;
    }

    private BlipView getBlipByMenuButton(Element menuButton) {
        Element eTimeArrow = menuButton.getParentElement();
        Element eMetaBar = eTimeArrow.getParentElement();
        Element eMeta = eMetaBar.getParentElement();
        Element eBlip = eMeta.getParentElement();
        return viewProvider.asBlip(eBlip);
    }

    private boolean isEnabled() {
        return !editSessionStarted || getBlip().isFocused();
    }
}