/******************************************************************************* * Copyright (c) 2016 itemis AG 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: * Matthias Wienand (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.fx.policies; import java.util.List; import org.eclipse.gef.mvc.fx.models.FocusModel; import org.eclipse.gef.mvc.fx.operations.ChangeFocusOperation; import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation; import org.eclipse.gef.mvc.fx.parts.IContentPart; import org.eclipse.gef.mvc.fx.parts.IRootPart; import org.eclipse.gef.mvc.fx.parts.IVisualPart; import org.eclipse.gef.mvc.fx.viewer.IViewer; import com.google.common.reflect.TypeToken; import javafx.collections.ObservableList; import javafx.scene.Node; /** * The {@link FocusTraversalPolicy} can be used to assign focus to the next or * previous part in the focus traversal cycle. * * @author mwienand * */ public class FocusTraversalPolicy extends AbstractPolicy { private IViewer viewer; private FocusModel focusModel; @Override protected ITransactionalOperation createOperation() { return new ChangeFocusOperation(viewer, null); } /** * Returns the inner most {@link IContentPart} child within the part * hierarchy of the given {@link IContentPart}. If the given * {@link IContentPart} does not have any {@link IContentPart} children, * then the given {@link IContentPart} is returned. * * @param part * The {@link IContentPart} for which to determine the inner most * {@link IContentPart} child. * @return The inner most {@link IContentPart} child within the part * hierarchy of the given {@link IContentPart}. */ protected IContentPart<? extends Node> findInnerMostContentPart( IContentPart<? extends Node> part) { ObservableList<IVisualPart<? extends Node>> children = part .getChildrenUnmodifiable(); while (!children.isEmpty()) { for (int i = children.size() - 1; i >= 0; i--) { IVisualPart<? extends Node> child = children.get(i); if (child instanceof IContentPart) { // continue searching for content part children within this // child's part hierarchy part = (IContentPart<? extends Node>) child; children = part.getChildrenUnmodifiable(); break; } } } // did not find a content part child => return the given content part return part; } /** * Determines the next {@link IContentPart} to which keyboard focus is * assigned, depending on the currently focused {@link IContentPart}. * <p> * The first content part child of the given focus part is returned as the * next part if a content part child is available. * <p> * The next content part sibling of the given focus part is returned as the * next part if a content part sibling is available. When one sibling list * ends, the search continues with the parent's siblings until it reaches * the root of the hierarchy. * <p> * If the next content part cannot be determined, <code>null</code> is * returned. * * @param current * The currently focused {@link IContentPart}. * @return The next {@link IContentPart} to which keyboard focus is * assigned, or <code>null</code> if no subsequent * {@link IContentPart} could be determined. */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected IContentPart<? extends Node> findNextContentPart( IContentPart<? extends Node> current) { // search children for content parts List<IVisualPart<? extends Node>> children = current .getChildrenUnmodifiable(); if (!children.isEmpty()) { for (IVisualPart<? extends Node> child : children) { if (child instanceof IContentPart) { return (IContentPart<? extends Node>) child; } } } // no content part children available, therefore, we have to search our // siblings and move up the hierarchy IVisualPart<? extends Node> parent = current.getParent(); while (parent instanceof IContentPart || parent instanceof IRootPart) { children = parent instanceof IContentPart ? parent.getChildrenUnmodifiable() : ((IRootPart) parent).getContentPartChildren(); int index = children.indexOf(current) + 1; while (index < children.size()) { IVisualPart<? extends Node> part = children.get(index); if (part instanceof IContentPart) { return (IContentPart<? extends Node>) part; } index++; } if (parent instanceof IContentPart) { current = (IContentPart<? extends Node>) parent; parent = current.getParent(); } else { return null; } } // could not find another content part return null; } /** * Determines the previous {@link IContentPart} to which keyboard focus is * assigned, depending on the currently focused {@link IContentPart}. * <p> * At first, the previous content part sibling of the given focus part is * determined. If a siblings list ends, the search continues with the * parent's siblings. * <p> * The inner most content part child of the previous content part sibling is * returned as the previous content part, or <code>null</code> if no * previous content part sibling could be found. * * @param current * The currently focused {@link IContentPart}. * @return The previous {@link IContentPart} to which keyboard focus is * assigned, or <code>null</code> if no previous * {@link IContentPart} could be determined. */ protected IContentPart<? extends Node> findPreviousContentPart( IContentPart<? extends Node> current) { // find previous content part sibling IVisualPart<? extends Node> parent = current.getParent(); if (parent instanceof IContentPart || parent instanceof IRootPart) { @SuppressWarnings({ "rawtypes", "unchecked" }) List<IVisualPart<? extends Node>> children = parent instanceof IContentPart ? parent.getChildrenUnmodifiable() : ((IRootPart) parent).getContentPartChildren(); int index = children.indexOf(current) - 1; while (index >= 0) { IVisualPart<? extends Node> part = children.get(index); if (part instanceof IContentPart) { return findInnerMostContentPart( (IContentPart<? extends Node>) part); } index--; } if (parent instanceof IContentPart) { return (IContentPart<? extends Node>) parent; } } // could not find a previous content part return null; } /** * Assigns focus to the next part in the traversal cycle. Returns the * {@link IContentPart} to which focus is assigned by the operation of this * policy, or <code>null</code> if focus is assigned to the viewer. * * @return The {@link IContentPart} to which focus is assigned by the * operation of this policy, or <code>null</code> if focus is * assigned to the viewer. */ public IContentPart<? extends Node> focusNext() { return traverse(false); } /** * Assigns focus to the previous part in the traversal cycle. Returns the * {@link IContentPart} to which focus is assigned by the operation of this * policy, or <code>null</code> if focus is assigned to the viewer. * * @return The {@link IContentPart} to which focus is assigned by the * operation of this policy, or <code>null</code> if focus is * assigned to the viewer. */ public IContentPart<? extends Node> focusPrevious() { return traverse(true); } /** * Returns the {@link ChangeFocusOperation} that is used to change the focus * part. * * @return The {@link ChangeFocusOperation} that is used to change the focus * part. */ protected ChangeFocusOperation getChangeFocusOperation() { return (ChangeFocusOperation) getOperation(); } @SuppressWarnings("serial") @Override public void init() { viewer = getHost().getRoot().getViewer(); focusModel = viewer.getAdapter(new TypeToken<FocusModel>() { }); super.init(); } /** * Traverses the focus forwards or backwards depending on the given flag. * Returns the {@link IContentPart} to which focus is assigned by the * operation of this policy, or <code>null</code> if focus is assigned to * the viewer. * * @param backwards * <code>true</code> if the focus should be traversed backwards, * otherwise <code>false</code>. * @return The {@link IContentPart} to which focus is assigned by the * operation of this policy, or <code>null</code> if focus is * assigned to the viewer. */ protected IContentPart<? extends Node> traverse(boolean backwards) { // get current focus part IContentPart<? extends Node> current = focusModel.getFocus(); IContentPart<? extends Node> next = null; // determine the first focus part if no part currently has focus if (current == null) { List<IContentPart<? extends Node>> children = viewer.getRootPart() .getContentPartChildren(); if (children != null && !children.isEmpty()) { if (backwards) { // focus last content leaf next = findInnerMostContentPart( children.get(children.size() - 1)); } else { // focus first content part next = children.get(0); } } } // find the next/previous part that is focusable if (current != null || next != null && !next.isFocusable()) { // in case we did not select a next part yet, start with the // currently focused part if (next == null) { next = current; } // search until a focusable part is found if (backwards) { do { next = findPreviousContentPart(next); } while (next != null && !next.isFocusable()); } else { do { next = findNextContentPart(next); } while (next != null && !next.isFocusable()); } } // give focus to the next part or to the viewer (if next is null) if (next == null || next.isFocusable()) { getChangeFocusOperation().setNewFocused(next); } return next; } }