/******************************************************************************* * Copyright (c) 2012-2017 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.data.tree.HasAction; import org.eclipse.che.ide.api.data.tree.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) { if (tree.getNodeDescriptor(node) == null) { continue; } 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)) { fireSelectionChange(); } } public List<Node> getSelectedNodes() { return Collections.unmodifiableList(selectionStorage); } }