| /******************************************************************************* |
| * Copyright (c) 2013, 2015, 2017 Obeo and others. |
| * 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: |
| * Obeo - initial API and implementation |
| * Michael Borkowski - bug 462237, refactoring |
| * Simon Delisle - bug 511172 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer; |
| |
| import static org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType.IN_UI_ASYNC; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.eclipse.compare.INavigatable; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.compare.DifferenceState; |
| import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType; |
| import org.eclipse.emf.compare.utils.EMFComparePredicates; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.OpenEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.swt.widgets.TreeItem; |
| |
| /** |
| * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> |
| */ |
| public class Navigatable implements INavigatable { |
| |
| public static final int NEXT_UNRESOLVED_CHANGE = 80; |
| |
| private final WrappableTreeViewer viewer; |
| |
| private final EMFCompareStructureMergeViewerContentProvider contentProvider; |
| |
| protected CallbackType uiSyncCallbackType = IN_UI_ASYNC; |
| |
| public Navigatable(WrappableTreeViewer viewer, |
| EMFCompareStructureMergeViewerContentProvider contentProvider) { |
| this.viewer = viewer; |
| this.contentProvider = contentProvider; |
| } |
| |
| public boolean selectChange(final int flag) { |
| contentProvider.runWhenReady(uiSyncCallbackType, new Runnable() { |
| public void run() { |
| TreeItem[] selection = viewer.getTree().getSelection(); |
| TreeItem firstSelectedItem = firstOrNull(selection); |
| Object newSelection = calculateNextSelection(firstSelectedItem, flag); |
| if (newSelection != null) { |
| fireOpen(newSelection); |
| } |
| } |
| }); |
| return false; |
| } |
| |
| private static TreeItem firstOrNull(TreeItem[] items) { |
| if (items.length > 0) { |
| return items[0]; |
| } |
| return null; |
| } |
| |
| private Object calculateNextSelection(TreeItem currentSelection, int flag) { |
| switch (flag) { |
| case NEXT_CHANGE: |
| return getNextDiff(thisOrFirstItem(currentSelection)); |
| case PREVIOUS_CHANGE: |
| return getPreviousDiff(thisOrFirstItem(currentSelection)); |
| case FIRST_CHANGE: |
| return getNextDiff(getFirstItemInTree()); |
| case LAST_CHANGE: |
| return getPreviousDiff(getFirstItemInTree()); |
| case NEXT_UNRESOLVED_CHANGE: |
| return getNextUnresolvedDiff(thisOrFirstItem(currentSelection)); |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| |
| private TreeItem thisOrFirstItem(TreeItem item) { |
| if (item != null) { |
| return item; |
| } |
| return getFirstItemInTree(); |
| } |
| |
| /** |
| * Execute the fireOpen method of the viewer associated to this navigatable. |
| * |
| * @param element |
| * the input of the selection of the open event fired by the fireOpen method. |
| */ |
| public void fireOpen(Object element) { |
| StructuredSelection newSelection = new StructuredSelection(element); |
| viewer.setSelection(newSelection); |
| viewer.fireOpen(new OpenEvent(viewer, newSelection)); |
| } |
| |
| /** |
| * Return the viewer associated with this Navigatable. |
| * |
| * @return the viewer associated with this Navigatable. |
| */ |
| public WrappableTreeViewer getViewer() { |
| return viewer; |
| } |
| |
| public Object getInput() { |
| return viewer.getInput(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.compare.INavigatable#openSelectedChange() |
| */ |
| public boolean openSelectedChange() { |
| ISelection selection = viewer.getSelection(); |
| if (selection.isEmpty()) { |
| return false; |
| } else { |
| viewer.fireOpen(new OpenEvent(viewer, selection)); |
| return true; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.compare.INavigatable#hasChange(int) |
| */ |
| public boolean hasChange(int changeFlag) { |
| TreeItem[] selection = viewer.getTree().getItems(); |
| TreeItem firstSelectedItem = selection.length > 0 ? selection[0] : null; |
| switch (changeFlag) { |
| case NEXT_CHANGE: |
| return getNextDiff(thisOrFirstItem(firstSelectedItem)) != null; |
| case PREVIOUS_CHANGE: |
| return getPreviousDiff(thisOrFirstItem(firstSelectedItem)) != null; |
| case NEXT_UNRESOLVED_CHANGE: |
| return getNextUnresolvedDiff(thisOrFirstItem(firstSelectedItem)) != null; |
| case FIRST_CHANGE: |
| TreeItem firstItemInTree = getFirstItemInTree(); |
| return firstItemInTree != null && getNextDiff(firstItemInTree) != null; |
| default: |
| throw new IllegalStateException(); |
| } |
| } |
| |
| /** |
| * Returns, from the given TreeNode, the next TreeNode that contains a diff. |
| * |
| * @param treeNode |
| * the given TreeNode for which we want to find the next. |
| * @return the next TreeNode that contains a diff. |
| */ |
| private Object getNextDiff(TreeItem start) { |
| return getNextData(start, Predicates.alwaysTrue()); |
| } |
| |
| /** |
| * Returns, from the given TreeNode, the previous TreeNode that contains a diff. |
| * |
| * @param treeNode |
| * the given TreeNode for which we want to find the previous. |
| * @return the previous TreeNode that contains a diff. |
| */ |
| private Object getPreviousDiff(TreeItem item) { |
| return getPreviousData(item, Predicates.alwaysTrue()); |
| } |
| |
| /** |
| * Returns, from the given TreeNode, the next TreeNode that contains an unresolved diff. |
| * |
| * @param treeNode |
| * the given TreeNode for which we want to find the next unresolvable diff. |
| * @return the previous TreeNode that contains an unresolvable diff. |
| */ |
| private Object getNextUnresolvedDiff(TreeItem start) { |
| return getNextData(start, EMFComparePredicates.hasState(DifferenceState.UNRESOLVED)); |
| } |
| |
| /** |
| * Returns the data of the next tree item meeting the supplied predicate |
| * |
| * @param start |
| * the TreeNode at which to start searching |
| * @param predicate |
| * the predicate to match |
| * @return the next matching TreeNode |
| */ |
| private Object getNextData(TreeItem start, Predicate<? super Diff> predicate) { |
| TreeItem current = getNextItem(start); |
| while (current != null) { |
| EObject data = EMFCompareStructureMergeViewer.getDataOfTreeNodeOfAdapter(current.getData()); |
| if (data instanceof Diff && predicate.apply((Diff)data)) { |
| return current.getData(); |
| } |
| current = getNextItem(current); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the data of the previous tree item meeting the supplied predicate |
| * |
| * @param start |
| * the TreeNode at which to start searching |
| * @param predicate |
| * the predicate to match |
| * @return the previous matching TreeNode |
| */ |
| private Object getPreviousData(TreeItem start, Predicate<? super Diff> predicate) { |
| TreeItem current = getPreviousItem(start); |
| while (current != null) { |
| EObject data = EMFCompareStructureMergeViewer.getDataOfTreeNodeOfAdapter(current.getData()); |
| if (data instanceof Diff && predicate.apply((Diff)data)) { |
| return current.getData(); |
| } |
| current = getPreviousItem(current); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the first TreeItem |
| * |
| * @return the first TreeItem |
| */ |
| private TreeItem getFirstItemInTree() { |
| final TreeItem startingItem; |
| TreeItem[] roots = viewer.getTree().getItems(); |
| if (roots != null && roots.length > 0) { |
| startingItem = roots[0]; |
| } else { |
| startingItem = null; |
| } |
| return startingItem; |
| } |
| |
| /** |
| * Starting at the given TreeItem, returns the next item in the tree. |
| * |
| * @param start |
| * the item for which to find the next one |
| * @return the next TreeItem |
| */ |
| // Protected for testing purposes |
| protected TreeItem getNextItem(TreeItem start) { |
| viewer.createChildren(start); |
| if (hasChildren(start)) { |
| return start.getItem(0); |
| } else if (hasNextSibling(start)) { |
| return getNextSibling(start); |
| } else { |
| return getNextAncestor(start); |
| } |
| } |
| |
| /** |
| * Checks whether the given item has children. |
| * |
| * @param item |
| * the item to check |
| * @return whether it has children |
| */ |
| private boolean hasChildren(TreeItem item) { |
| return item.getItems().length != 0; |
| } |
| |
| /** |
| * Checks whether the given item has a next sibling. |
| * |
| * @param item |
| * the item to check |
| * @return whether it has a next sibling |
| */ |
| private boolean hasNextSibling(TreeItem item) { |
| return getNextSibling(item) != null; |
| } |
| |
| /** |
| * Returns the next ancestor of the given TreeItem. "Next" means that it is the closest available sibling |
| * for one of the given TreeNode's ancestors. |
| * |
| * @param item |
| * the item for which to get the next ancestor |
| * @return the next ancestor |
| */ |
| private TreeItem getNextAncestor(TreeItem item) { |
| TreeItem nextAncestor = null; |
| TreeItem ancestor = item.getParentItem(); |
| while (ancestor != null && nextAncestor == null) { |
| nextAncestor = getNextSibling(ancestor); |
| ancestor = ancestor.getParentItem(); |
| } |
| return nextAncestor; |
| } |
| |
| /** |
| * Starting at the given TreeItem, returns the previous item in the tree. |
| * |
| * @param start |
| * the item for which to find the previous one |
| * @return the previous TreeItem |
| */ |
| // Protected for testing purposes |
| protected TreeItem getPreviousItem(TreeItem start) { |
| if (hasPreviousSibling(start)) { |
| return getLastDescendant(getPreviousSibling(start)); |
| } else { |
| return start.getParentItem(); |
| } |
| } |
| |
| /** |
| * Checks whether the given item has a previous sibling. |
| * |
| * @param item |
| * the item to check |
| * @return whether it has a previous sibling |
| */ |
| private boolean hasPreviousSibling(TreeItem item) { |
| return getPreviousSibling(item) != null; |
| } |
| |
| /** |
| * Returns the last item of a depth-first search starting at the given TreeItem |
| * |
| * @param input |
| * the starting item |
| * @return the resulting item |
| */ |
| private TreeItem getLastDescendant(TreeItem input) { |
| TreeItem[] children = getChildren(input); |
| TreeItem deepestChild = input; |
| while (children.length > 0) { |
| deepestChild = children[children.length - 1]; |
| children = getChildren(deepestChild); |
| } |
| return deepestChild; |
| } |
| |
| /** |
| * Return a (possibly empty) array of TreeItem which are the children of the input item. This loads the |
| * children in the tree before returning the array to avoid passing over hidden items (Bug 511172). |
| * |
| * @param input |
| * TreeItem that you want to have the children |
| * @return Direct item children of the input |
| */ |
| private TreeItem[] getChildren(TreeItem input) { |
| viewer.createChildren(input); |
| return input.getItems(); |
| } |
| |
| /** |
| * Returns the previous sibling of the given TreeItem. |
| * |
| * @param item |
| * the item to get the sibling for |
| * @return the sibling |
| */ |
| private TreeItem getPreviousSibling(TreeItem item) { |
| List<TreeItem> siblings = Arrays.asList(getSiblings(item)); |
| int indexOfCurrent = siblings.indexOf(item); |
| if (indexOfCurrent > 0) { |
| return siblings.get(indexOfCurrent - 1); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the next sibling of the given TreeItem. |
| * |
| * @param item |
| * the item to get the sibling for |
| * @return the sibling |
| */ |
| private TreeItem getNextSibling(TreeItem item) { |
| List<TreeItem> siblings = Arrays.asList(getSiblings(item)); |
| int indexOfItem = siblings.indexOf(item); |
| if (indexOfItem >= 0 && indexOfItem < siblings.size() - 1) { |
| return siblings.get(indexOfItem + 1); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the siblings for a given TreeItem (including the item itself). |
| * |
| * @param item |
| * the item to get the siblings for |
| * @return the siblings |
| */ |
| private TreeItem[] getSiblings(TreeItem item) { |
| if (item.getParentItem() == null) { |
| return viewer.getTree().getItems(); |
| } else { |
| return item.getParentItem().getItems(); |
| } |
| } |
| } |