org.eclipse.che.ide.ui.smartTree.SelectionModel.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.ide.ui.smartTree.SelectionModel.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2016 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.ui.smartTree;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
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.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.HasBeforeSelectionHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;

import org.eclipse.che.ide.api.project.node.HasAction;
import org.eclipse.che.ide.api.project.node.Node;
import org.eclipse.che.ide.ui.smartTree.handler.GroupingHandlerRegistration;
import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent;
import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent.HasSelectionChangedHandlers;
import org.eclipse.che.ide.ui.smartTree.event.SelectionChangedEvent.SelectionChangedHandler;
import org.eclipse.che.ide.ui.smartTree.event.StoreAddEvent;
import org.eclipse.che.ide.ui.smartTree.event.StoreAddEvent.StoreAddHandler;
import org.eclipse.che.ide.ui.smartTree.event.StoreClearEvent;
import org.eclipse.che.ide.ui.smartTree.event.StoreClearEvent.StoreClearHandler;
import org.eclipse.che.ide.ui.smartTree.event.StoreRecordChangeEvent;
import org.eclipse.che.ide.ui.smartTree.event.StoreRecordChangeEvent.StoreRecordChangeHandler;
import org.eclipse.che.ide.ui.smartTree.event.StoreRemoveEvent;
import org.eclipse.che.ide.ui.smartTree.event.StoreRemoveEvent.StoreRemoveHandler;
import org.eclipse.che.ide.ui.smartTree.event.StoreUpdateEvent;
import org.eclipse.che.ide.ui.smartTree.event.StoreUpdateEvent.StoreUpdateHandler;
import org.eclipse.che.ide.ui.smartTree.event.internal.NativeTreeEvent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author Vlad Zhukovskiy
 */
public class SelectionModel
        implements HasSelectionHandlers<Node>, HasBeforeSelectionHandlers<Node>, HasSelectionChangedHandlers {
    private class TreeMouseHandler implements MouseDownHandler, ClickHandler {
        @Override
        public void onClick(ClickEvent event) {
        }

        @Override
        public void onMouseDown(MouseDownEvent event) {
            SelectionModel.this.onMouseDown(event);
        }
    }

    private class TreeStorageHandler implements StoreAddHandler, StoreRemoveHandler, StoreClearHandler,
            StoreRecordChangeHandler, StoreUpdateHandler {

        @Override
        public void onAdd(StoreAddEvent event) {
            SelectionModel.this.onAdd(event.getNodes());
        }

        @Override
        public void onClear(StoreClearEvent event) {
            SelectionModel.this.onClear(event);
        }

        @Override
        public void onRecordChange(final StoreRecordChangeEvent event) {
            Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
                @Override
                public void execute() {
                    SelectionModel.this.onRecordChange(event);
                }
            });
        }

        @Override
        public void onRemove(StoreRemoveEvent event) {
            SelectionModel.this.onRemove(event.getNode());
        }

        @Override
        public void onUpdate(StoreUpdateEvent event) {
            final List<Node> update = event.getNodes();
            // run defer to ensure the code runs after grid view refreshes row
            Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
                @Override
                public void execute() {
                    for (Node anUpdate : update) {
                        SelectionModel.this.onUpdate(anUpdate);
                    }
                }
            });
        }

    }

    protected KeyboardNavigationHandler keyNav = new KeyboardNavigationHandler() {
        /** {@inheritDoc} */
        @Override
        public void onDown(NativeEvent evt) {
            onKeyDown(evt);
        }

        /** {@inheritDoc} */
        @Override
        public void onLeft(NativeEvent evt) {
            onKeyLeft(evt);
        }

        /** {@inheritDoc} */
        @Override
        public void onRight(NativeEvent evt) {
            onKeyRight(evt);
        }

        /** {@inheritDoc} */
        @Override
        public void onUp(NativeEvent evt) {
            onKeyUp(evt);
        }

        @Override
        public void onEnd(NativeEvent evt) {
            onKeyEnd(evt);
        }

        @Override
        public void onHome(NativeEvent evt) {
            onKeyHome(evt);
        }

        @Override
        public void onPageDown(NativeEvent evt) {
            onKeyPageDown(evt);
        }

        @Override
        public void onPageUp(NativeEvent evt) {
            onKeyPageUp(evt);
        }

        @Override
        public void onEsc(NativeEvent evt) {
            onKeyEsc(evt);
        }

        @Override
        public void onEnter(NativeEvent evt) {
            onKeyEnter(evt);
        }
    };

    protected Tree tree;

    protected NodeStorage nodeStorage;

    private TreeMouseHandler treeMouseHandler = new TreeMouseHandler();
    private TreeStorageHandler treeStorageHandler = new TreeStorageHandler();
    private GroupingHandlerRegistration handlerRegistration;
    protected List<Node> selectionStorage = new ArrayList<>();
    protected Node lastSelectedNode;
    protected Mode selectionMode = Mode.MULTI;
    private Node lastFocused;
    private HandlerManager handlerManager;
    protected boolean mouseDown;
    protected boolean fireSelectionChangeOnClick;

    public static enum Mode {
        SINGLE, SIMPLE, MULTI
    }

    public SelectionModel() {

    }

    /** {@inheritDoc} */
    @Override
    public HandlerRegistration addSelectionHandler(SelectionHandler<Node> handler) {
        return ensureHandlers().addHandler(SelectionEvent.getType(), handler);
    }

    /** {@inheritDoc} */
    @Override
    public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<Node> handler) {
        return ensureHandlers().addHandler(BeforeSelectionEvent.getType(), handler);
    }

    /** {@inheritDoc} */
    @Override
    public HandlerRegistration addSelectionChangedHandler(SelectionChangedHandler handler) {
        return ensureHandlers().addHandler(SelectionChangedEvent.getType(), handler);
    }

    protected HandlerManager ensureHandlers() {
        if (handlerManager == null) {
            handlerManager = new HandlerManager(this);
        }
        return handlerManager;
    }

    /** {@inheritDoc} */
    @Override
    public void fireEvent(GwtEvent<?> event) {
        if (handlerManager != null) {
            handlerManager.fireEvent(event);
        }
    }

    public void bindTree(Tree tree) {
        if (this.tree != null) {
            handlerRegistration.removeHandler();
            keyNav.bind(null);
            bindStorage(null);
            nodeStorage = null;
        }

        this.tree = tree;

        if (tree != null) {
            if (handlerRegistration == null) {
                handlerRegistration = new GroupingHandlerRegistration();
            }
            handlerRegistration.add(tree.addDomHandler(treeMouseHandler, MouseDownEvent.getType()));
            handlerRegistration.add(tree.addDomHandler(treeMouseHandler, ClickEvent.getType()));
            keyNav.bind(tree);
            bindStorage(tree.getNodeStorage());
            nodeStorage = tree.getNodeStorage();
        }
    }

    public void bindStorage(NodeStorage store) {
        deselectAll();
        if (this.nodeStorage != null) {
            handlerRegistration.removeHandler();
        }
        this.nodeStorage = store;
        if (store != null) {
            if (handlerRegistration == null) {
                handlerRegistration = new GroupingHandlerRegistration();
            }

            handlerRegistration.add(store.addStoreAddHandler(treeStorageHandler));
            handlerRegistration.add(store.addStoreRemoveHandler(treeStorageHandler));
            handlerRegistration.add(store.addStoreClearHandler(treeStorageHandler));
            handlerRegistration.add(store.addStoreUpdateHandler(treeStorageHandler));
            handlerRegistration.add(store.addStoreRecordChangeHandler(treeStorageHandler));
        }
    }

    public Tree getTree() {
        return tree;
    }

    public boolean isSelected(Node node) {
        return selectionStorage.contains(node);
    }

    public void selectNext() {
        Node next = next();
        if (next != null) {
            doSingleSelect(next, false);
        }
    }

    public void selectPrevious() {
        Node prev = prev();
        if (prev != null) {
            doSingleSelect(prev, false);
        }
    }

    protected Node next() {
        Node sel = lastSelectedNode;
        if (sel == null) {
            return null;
        }

        Node first = nodeStorage.getFirstChild(sel);

        if (first != null && tree.isExpanded(sel)) {
            return first;
        } else {
            Node nextSibling = nodeStorage.getNextSibling(sel);
            if (nextSibling != null) {
                return nextSibling;
            } else {
                Node p = nodeStorage.getParent(sel);
                while (p != null) {
                    nextSibling = nodeStorage.getNextSibling(p);
                    if (nextSibling != null) {
                        return nextSibling;
                    }
                    p = nodeStorage.getParent(p);
                }
            }
        }
        return null;
    }

    protected Node prev() {
        Node sel = lastSelectedNode;
        if (sel == null) {
            return null;
        }
        Node prev = nodeStorage.getPreviousSibling(sel);
        if (prev != null) {
            if ((!tree.isExpanded(prev) || nodeStorage.getChildCount(prev) < 1)) {
                return prev;
            } else {
                Node lastChild = nodeStorage.getLastChild(prev);
                while (lastChild != null && nodeStorage.getChildCount(lastChild) > 0
                        && tree.isExpanded(lastChild)) {
                    lastChild = nodeStorage.getLastChild(lastChild);
                }
                return lastChild;
            }
        } else {
            Node parent = nodeStorage.getParent(sel);
            if (parent != null) {
                return parent;
            }
        }
        return null;
    }

    protected void onKeyDown(NativeEvent e) {
        e.preventDefault();
        Node next = next();
        if (next != null) {
            doSingleSelect(next, false);
            tree.scrollIntoView(next);
        }
    }

    protected void onKeyLeft(NativeEvent ce) {
        ce.preventDefault();
        if (lastSelectedNode != null && !tree.isLeaf(lastSelectedNode) && tree.isExpanded(lastSelectedNode)) {
            tree.setExpanded(lastSelectedNode, false, true);
        } else if (lastSelectedNode != null && nodeStorage.getParent(lastSelectedNode) != null) {
            doSingleSelect(nodeStorage.getParent(lastSelectedNode), false);
        }
    }

    protected void onKeyRight(NativeEvent ce) {
        ce.preventDefault();
        if (lastSelectedNode != null && !tree.isLeaf(lastSelectedNode) && !tree.isExpanded(lastSelectedNode)) {
            tree.setExpanded(lastSelectedNode, true);
        }
    }

    protected void onKeyUp(NativeEvent ke) {
        NativeTreeEvent e = ke.cast();
        e.preventDefault();
        Node prev = prev();
        if (prev != null) {
            doSingleSelect(prev, false);
            tree.scrollIntoView(prev);
        }
    }

    private void onKeyEsc(NativeEvent evt) {
        evt.preventDefault();
        deselectAll();
    }

    private void onKeyEnter(NativeEvent evt) {
        for (Node node : selectionStorage) {
            if (node instanceof HasAction) {
                ((HasAction) node).actionPerformed();
            }

            if (!node.isLeaf()) {
                tree.toggle(node);
            }
        }
    }

    private void onKeyEnd(NativeEvent evt) {
        evt.preventDefault();
        //TODO implement this feature
    }

    private void onKeyHome(NativeEvent evt) {
        evt.preventDefault();

        //TODO implement this feature
    }

    private void onKeyPageDown(NativeEvent evt) {
        evt.preventDefault();
        //TODO implement this feature
    }

    private void onKeyPageUp(NativeEvent evt) {
        evt.preventDefault();
        //TODO implement this feature
    }

    protected void onMouseClick(ClickEvent ce) {
        NativeTreeEvent e = ce.getNativeEvent().cast();

        if (fireSelectionChangeOnClick) {
            fireSelectionChange();
            fireSelectionChangeOnClick = false;
        }

        if (selectionMode == Mode.MULTI) {
            NodeDescriptor node = tree.getNodeDescriptor((Element) e.getEventTarget().cast());
            // on dnd prevent drag the node will be null
            if (node != null) {
                Node sel = node.getNode();
                if (e.getCtrlOrMetaKey() && isSelected(sel)) {
                    doDeselect(Collections.singletonList(sel), false);
                    tree.focus();

                    // reset the starting location of the click when meta is used during a multiselect
                    lastSelectedNode = sel;
                } else if (e.getCtrlOrMetaKey()) {
                    doSelect(Collections.singletonList(sel), true, false);
                    tree.focus();

                    // reset the starting location of the click when meta is used during a multiselect
                    lastSelectedNode = sel;
                } else if (isSelected(sel) && !e.getShiftKey() && !e.getCtrlOrMetaKey()
                        && selectionStorage.size() > 0) {
                    doSelect(Collections.singletonList(sel), false, false);
                    tree.focus();
                }
            }
        }
    }

    protected void onMouseDown(MouseDownEvent mde) {
        NativeTreeEvent e = mde.getNativeEvent().cast();
        Element target = e.getEventTargetEl();
        NodeDescriptor selNode = tree.getNodeDescriptor(target);

        if (selNode == null || tree == null) {
            return;
        }

        Node sel = selNode.getNode();
        if (!tree.getView().isSelectableTarget(sel, target)) {
            return;
        }

        boolean isSelected = isSelected(sel);
        boolean isMeta = e.getCtrlOrMetaKey();
        boolean isShift = e.getShiftKey();

        if (e.isRightClick() && isSelected) {
            return;
        } else {
            switch (selectionMode) {
            case SIMPLE:
                tree.focus();
                if (isSelected(sel)) {
                    deselect(sel);
                } else {
                    doSelect(Collections.singletonList(sel), true, false);
                }
                break;

            case SINGLE:
                tree.focus();
                if (isMeta && isSelected) {
                    deselect(sel);
                } else if (!isSelected) {
                    select(sel, false);
                }
                break;

            case MULTI:
                if (isShift && lastSelectedNode != null) {
                    List<Node> selectedItems = new ArrayList<>();

                    // from last selected or firstly selected
                    NodeDescriptor lastSelTreeNode = tree.getNodeDescriptor(lastSelectedNode);
                    Element lastSelTreeEl = tree.getView().getRootContainer(lastSelTreeNode);

                    // to selected or secondly selected
                    NodeDescriptor selTreeNode = tree.getNodeDescriptor(sel);
                    Element selTreeNodeEl = tree.getView().getRootContainer(selTreeNode);

                    // holding shift down, selecting the same item again, selecting itself
                    if (sel == lastSelectedNode) {
                        tree.focus();
                        doSelect(Collections.singletonList(sel), false, false);

                    } else if (lastSelTreeEl != null && selTreeNodeEl != null) {
                        // add the last selected, as its not added during the walk
                        selectedItems.add(lastSelectedNode);

                        // After walking reset back to previously selected
                        final Node previouslyLastSelected = lastSelectedNode;

                        // This deals with flipping directions
                        if (lastSelTreeEl.getAbsoluteTop() < selTreeNodeEl.getAbsoluteTop()) {
                            // down selection
                            Node next = next();
                            while (next != null) {
                                selectedItems.add(next);
                                lastSelectedNode = next;
                                if (next == sel)
                                    break;
                                next = next();
                            }

                        } else {
                            // up selection
                            Node prev = prev();
                            while (prev != null) {
                                selectedItems.add(prev);
                                lastSelectedNode = prev;
                                if (prev == sel)
                                    break;
                                prev = prev();
                            }
                        }

                        tree.focus();
                        doSelect(selectedItems, false, false);

                        // change back to last selected, the walking causes this need
                        lastSelectedNode = previouslyLastSelected;
                    }

                } else if (!isSelected(sel)) {
                    tree.focus();
                    doSelect(Collections.singletonList(sel), e.getCtrlOrMetaKey(), false);

                    // reset the starting location of multi select
                    lastSelectedNode = sel;
                } else if (isSelected(sel) && !e.getShiftKey() && !e.getCtrlOrMetaKey()
                        && !selectionStorage.isEmpty()) {
                    doSelect(Collections.singletonList(sel), false, false);
                    tree.focus();
                } else if (isSelected(sel) && !selectionStorage.isEmpty()) {
                    doDeselect(Collections.singletonList(sel), false);
                }
                break;
            }
        }

        mouseDown = false;
    }

    protected void onSelectChange(Node node, boolean select) {
        tree.getView().onSelectChange(node, select);
    }

    public void deselectAll() {
        doDeselect(new ArrayList<>(selectionStorage), false);
    }

    protected void doDeselect(List<Node> nodes, boolean suppressEvent) {
        boolean change = false;
        for (Node node : nodes) {
            if (selectionStorage.remove(node)) {
                if (lastSelectedNode == node) {
                    lastSelectedNode = selectionStorage.size() > 0
                            ? selectionStorage.get(selectionStorage.size() - 1)
                            : null;
                }
                onSelectChange(node, false);
                change = true;
            }
        }
        if (!suppressEvent && change) {
            fireSelectionChange();
        }
    }

    protected void doSelect(List<Node> nodes, boolean keepExisting, boolean suppressEvent) {
        if (selectionMode == Mode.SINGLE) {
            Node node = nodes.size() > 0 ? nodes.get(0) : null;
            if (node != null) {
                doSingleSelect(node, suppressEvent);
            }
        } else {
            doMultiSelect(nodes, keepExisting, suppressEvent);
        }
    }

    protected void doSingleSelect(Node node, boolean suppressEvent) {
        int index;
        index = nodeStorage.indexOf(node);
        if (index == -1 || isSelected(node)) {
            return;
        } else {
            if (!suppressEvent) {
                BeforeSelectionEvent<Node> evt = BeforeSelectionEvent.fire(this, node);
                if (evt != null && evt.isCanceled()) {
                    return;
                }
            }
        }

        boolean change = false;
        if (selectionStorage.size() > 0 && !isSelected(node)) {
            doDeselect(Collections.singletonList(lastSelectedNode), true);
            change = true;
        }

        if (selectionStorage.size() == 0) {
            change = true;
        }

        selectionStorage.add(node);
        lastSelectedNode = node;
        onSelectChange(node, true);
        setLastFocused(lastSelectedNode);

        if (!suppressEvent) {
            SelectionEvent.fire(this, node);
        }

        if (change && !suppressEvent) {
            fireSelectionChange();
        }
    }

    protected void doMultiSelect(List<Node> nodes, boolean keepExisting, boolean suppressEvent) {
        boolean change = false;
        if (!keepExisting && selectionStorage.size() > 0) {
            change = true;
            doDeselect(new ArrayList<>(selectionStorage), true);
        }

        for (Node node : nodes) {
            boolean isSelected = isSelected(node);
            if (!suppressEvent && !isSelected) {
                BeforeSelectionEvent<Node> evt = BeforeSelectionEvent.fire(this, node);
                if (evt != null && evt.isCanceled()) {
                    continue;
                }
            }

            change = true;
            lastSelectedNode = node;

            selectionStorage.add(node);
            setLastFocused(lastSelectedNode);

            if (!isSelected) {
                onSelectChange(node, true);
                if (!suppressEvent) {
                    SelectionEvent.fire(this, node);
                }
            }
        }

        if (change && !suppressEvent) {
            fireSelectionChange();
        }
    }

    public void deselect(Node item) {
        deselect(Collections.singletonList(item));
    }

    public void deselect(List<Node> items) {
        doDeselect(items, false);
    }

    public void deselect(Node... items) {
        deselect(Arrays.asList(items));
    }

    public void select(Node item, boolean keepExisting) {
        select(Collections.singletonList(item), keepExisting);
    }

    public void select(List<Node> items, boolean keepExisting) {
        doSelect(items, keepExisting, false);
    }

    protected void setLastFocused(Node lastFocused) {
        Node lf = this.lastFocused;
        this.lastFocused = lastFocused;

        onLastFocusedChange(lf, lastFocused);
    }

    public void refresh() {
        List<Node> sel = new ArrayList<>();
        boolean change = false;
        for (Node node : selectionStorage) {
            Node storeNode = nodeStorage.findNode(node);
            if (storeNode != null) {
                sel.add(storeNode);
            }
        }

        if (sel.size() != selectionStorage.size()) {
            change = true;
        }

        selectionStorage.clear();
        lastSelectedNode = null;
        setLastFocused(null);
        doSelect(sel, false, true);
        if (change) {
            fireSelectionChange();
        }
    }

    protected void fireSelectionChange() {
        if (mouseDown) {
            fireSelectionChangeOnClick = true;
        } else {
            fireEvent(new SelectionChangedEvent(selectionStorage));
        }
    }

    protected void onLastFocusedChange(Node oldFocused, Node newFocused) {
        //temporary stub
    }

    public Mode getSelectionMode() {
        return selectionMode;
    }

    public void setSelectionMode(Mode selectionMode) {
        this.selectionMode = selectionMode;
    }

    public void setSelection(List<Node> selection) {
        select(selection, false);
    }

    protected void onAdd(List<? extends Node> models) {
        //temporary stub
    }

    protected void onClear(StoreClearEvent event) {
        int oldSize = selectionStorage.size();
        selectionStorage.clear();
        lastSelectedNode = null;
        setLastFocused(null);
        if (oldSize > 0)
            fireSelectionChange();
    }

    protected void onRecordChange(StoreRecordChangeEvent event) {
        //temporary stub
    }

    protected Node getLastFocused() {
        return lastFocused;
    }

    protected void onUpdate(Node model) {
        for (int i = 0; i < selectionStorage.size(); i++) {
            Node m = selectionStorage.get(i);
            if (nodeStorage.hasMatchingKey(model, m)) {
                if (m != model) {
                    selectionStorage.remove(m);
                    selectionStorage.add(i, model);
                }
                if (lastSelectedNode == m) {
                    lastSelectedNode = model;
                }
                break;
            }
        }
        if (getLastFocused() != null && model != getLastFocused()
                && nodeStorage.hasMatchingKey(model, getLastFocused())) {
            lastFocused = model;
        }
    }

    protected void onRemove(Node model) {
        if (selectionStorage.remove(model)) {
            if (lastSelectedNode == model) {
                lastSelectedNode = null;
            }
            if (getLastFocused() == model) {
                setLastFocused(null);
            }
            fireSelectionChange();
        }
    }

    public List<Node> getSelectedNodes() {
        return Collections.unmodifiableList(selectionStorage);
    }
}