/******************************************************************************* * Copyright (c) 2007, 2014 compeople 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.navigation.model; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import java.util.Vector; import org.osgi.service.log.LogService; import org.eclipse.core.runtime.Assert; import org.eclipse.equinox.log.Logger; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.core.marker.IMarker; import org.eclipse.riena.core.util.Iter; import org.eclipse.riena.core.util.Nop; import org.eclipse.riena.core.util.Trace; import org.eclipse.riena.internal.navigation.Activator; import org.eclipse.riena.navigation.IApplicationNode; import org.eclipse.riena.navigation.IJumpTargetListener; import org.eclipse.riena.navigation.IJumpTargetListener.JumpTargetState; import org.eclipse.riena.navigation.IModuleGroupNode; import org.eclipse.riena.navigation.IModuleNode; import org.eclipse.riena.navigation.INavigationContext; import org.eclipse.riena.navigation.INavigationHistoryEvent; import org.eclipse.riena.navigation.INavigationHistoryListener; import org.eclipse.riena.navigation.INavigationNode; import org.eclipse.riena.navigation.INavigationNode.State; import org.eclipse.riena.navigation.INavigationNodeProvider; import org.eclipse.riena.navigation.INavigationProcessor; import org.eclipse.riena.navigation.ISubApplicationNode; import org.eclipse.riena.navigation.ISubModuleNode; import org.eclipse.riena.navigation.NavigationArgument; import org.eclipse.riena.navigation.NavigationNodeId; import org.eclipse.riena.ui.core.marker.DisabledMarker; import org.eclipse.riena.ui.core.marker.HiddenMarker; import org.eclipse.riena.ui.ridgets.IRidget; import org.eclipse.riena.ui.ridgets.IRidgetContainer; /** * Default implementation for the navigation processor */ public class NavigationProcessor implements INavigationProcessor { private List<ISubModuleNode> collNodes; private final Stack<INavigationNode<?>> histBack = new Stack<INavigationNode<?>>(); private final Stack<INavigationNode<?>> histForward = new Stack<INavigationNode<?>>(); private final Map<INavigationNode<?>, Stack<JumpContext>> jumpTargets = new HashMap<INavigationNode<?>, Stack<JumpContext>>(); private final Map<INavigationNode<?>, List<IJumpTargetListener>> jumpTargetListeners = new HashMap<INavigationNode<?>, List<IJumpTargetListener>>(); private final Map<INavigationNode<?>, INavigationNode<?>> navigationMap = new HashMap<INavigationNode<?>, INavigationNode<?>>(); private final List<INavigationHistoryListener> navigationListener = new Vector<INavigationHistoryListener>(); private final static int MAX_HISTORY_LENGTH = 40; private final static boolean DEBUG_NAVIGATION_PROCESSOR = Trace.isOn(NavigationProcessor.class, "debug"); //$NON-NLS-1$ private final static Logger LOGGER = Log4r.getLogger(Activator.getDefault(), NavigationProcessor.class); public void activate(final INavigationNode<?> toActivate) { if (toActivate != null) { final IModuleNode moduleNode = toActivate.getParentOfType(IModuleNode.class); collNodes = collectCollapsedNodes(moduleNode); if (toActivate.isActivated()) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - activate triggered for Node " + toActivate.getNodeId() + "but is already activated --> NOP"); //$NON-NLS-1$//$NON-NLS-2$ } Nop.reason("see comment below."); //$NON-NLS-1$ // do nothing // if toActivate is module, module group or sub application // the same sub module will be activated on activation of // the toActivate, in any case there is nothing to do } else { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - activate triggered for Node " + toActivate.getNodeId()); //$NON-NLS-1$ } if (!toActivate.isVisible() || !toActivate.isEnabled()) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - activate triggered for Node " + toActivate.getNodeId() + "but is not visible or not enabled --> NOP"); //$NON-NLS-1$//$NON-NLS-2$ } return; } // 1.find the chain to activate // 2.find the chain to deactivate // 3.check the deactivation chain // 4.check the activation chain // 5. do the deactivation chain // 6. hande node on the histBack // 7. do the activation chain final List<INavigationNode<?>> toActivateList = getNodesToActivateOnActivation(toActivate); if (toActivateList.isEmpty()) { return; } final List<INavigationNode<?>> toDeactivateList = getNodesToDeactivateOnActivation(toActivate); INavigationContext navigationContext = new NavigationContext(null, toActivateList, toDeactivateList); if (allowsDeactivate(navigationContext)) { if (allowsActivate(navigationContext)) { // the activation chain may have changed -> update context navigationContext = new NavigationContext(null, getNodesToActivateOnActivation(toActivate), getNodesToDeactivateOnActivation(toActivate)); deactivate(navigationContext); activate(navigationContext); } else { final INavigationNode<?> currentActive = getActiveChild(toActivate.getParent()); if (currentActive == null) { return; } toActivateList.clear(); toActivateList.add(currentActive); toDeactivateList.clear(); navigationContext = new NavigationContext(null, toActivateList, toDeactivateList); activate(navigationContext); currentActive.onAfterActivate(navigationContext); } } } } } /** * @since 3.0 */ public void markNodesToCollapse(final INavigationNode<?> toActivate) { if (toActivate.getContext("fromUI") == null) { //$NON-NLS-1$ if (collNodes != null) { for (final ISubModuleNode node : collNodes) { if (node.isExpanded()) { node.setCloseSubTree(true); } } } } else { toActivate.removeContext("fromUI"); //$NON-NLS-1$ } } private boolean isJumpSource(final ISubModuleNode node) { final Iterator<Map.Entry<INavigationNode<?>, Stack<JumpContext>>> it = jumpTargets.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<INavigationNode<?>, Stack<JumpContext>> entry = it.next(); final Stack<JumpContext> value = entry.getValue(); if (!value.isEmpty() && value.peek().getSource().equals(node)) { return true; } } return false; } private boolean isNodeOrChildJumpSource(final ISubModuleNode node) { final List<ISubModuleNode> children = node.getChildren(); if (isJumpSource(node)) { exemptNodeFromCollapsing(node); return true; } for (final ISubModuleNode child : children) { if (isJumpSource(child)) { node.setCloseSubTree(false); exemptNodeFromCollapsing(child); return true; } } return false; } private void exemptNodeFromCollapsing(final ISubModuleNode node) { node.setCloseSubTree(false); ISubModuleNode parent = node.getParentOfType(ISubModuleNode.class); while (parent != null) { if (collNodes.contains(parent)) { parent.setCloseSubTree(false); } parent = parent.getParentOfType(ISubModuleNode.class); } } private List<ISubModuleNode> collectCollapsedNodes(final INavigationNode<?> node) { final List<ISubModuleNode> collapsedNodes = new LinkedList<ISubModuleNode>(); List<ISubModuleNode> children = new LinkedList<ISubModuleNode>(); if (node instanceof IModuleNode) { children = ((IModuleNode) node).getChildren(); } else if (node instanceof ISubModuleNode) { children = ((ISubModuleNode) node).getChildren(); } for (final ISubModuleNode child : children) { if (!collapsedNodes.contains(child) && (!child.isExpanded() || child.isCloseSubTree())) { if (!isNodeOrChildJumpSource(child)) { collapsedNodes.add(child); } } if (!child.isLeaf()) { collapsedNodes.addAll(collectCollapsedNodes(child)); } } return collapsedNodes; } /** * @since 2.0 */ public void prepare(final INavigationNode<?> toPrepare) { final List<INavigationNode<?>> toPreparedList = new LinkedList<INavigationNode<?>>(); toPreparedList.add(toPrepare); final INavigationContext navigationContext = new NavigationContext(toPreparedList, null, null); toPrepare.prepare(navigationContext); } /** * Remembers the node to push onto the navigation history stack. A maximum of MAX_STACKSIZE elements are stored. Older elements are removed from the stack. * * @param toActivate */ private void buildHistory(final INavigationNode<?> toActivate) { // filter out unnavigatable nodes if (!(toActivate instanceof ISubModuleNode) || toActivate.isDisposed()) { return; } if (histBack.isEmpty() || !histBack.peek().equals(toActivate)) { histBack.push(toActivate); // limit the stack size and remove older elements if (histBack.size() > MAX_HISTORY_LENGTH) { histBack.remove(histBack.firstElement()); } fireBackHistoryChangedEvent(); } // is forwarding history top stack element equals node toActivate, // remove it! if (!histForward.isEmpty() && histForward.peek().equals(toActivate)) { histForward.pop();// remove newest node fireForewardHistoryChangedEvent(); } } /** * @since 3.0 */ public void dispose(final INavigationNode<?> toDispose) { // 1. check which nodes are active from the node toDispose and all its // children must be deactivated // 2. find nodes to activate automatically // 3. check if the new nodes can be activated // 4. deactivate the previously active nodes // 5. dispose the nodes // 6. remove the nodes from the tree // 7. activate the other node // Special handling: if the node toDispose is the first module in a // moduleGroup, // than the whole Module Group has to be disposed // if there was no sub module active in the module, // than no other module has to be activated final INavigationNode<?> nodeToDispose = getNodeToDispose(toDispose); if (nodeToDispose != null && !nodeToDispose.isDisposed()) { final State nodeStartState = nodeToDispose.getState(); final IApplicationNode applicationNode = nodeToDispose.getParentOfType(IApplicationNode.class); handleJumpsOnDispose(nodeToDispose); final List<INavigationNode<?>> toDeactivateList = getNodesToDeactivateOnDispose(nodeToDispose); Boolean oldSelectable = null; if (toDispose instanceof ISubModuleNode) { oldSelectable = ((ISubModuleNode) toDispose).isSelectable(); ((ISubModuleNode) toDispose).setSelectable(false); } List<INavigationNode<?>> toActivateList = null; try { toActivateList = getNodesToActivateOnDispose(nodeToDispose); } catch (final NavigationModelFailure ex) { String msg = "Dispose is not allowed for: " + toDispose.toString(); //$NON-NLS-1$ msg += "\n" + ex.getMessage(); //$NON-NLS-1$ LOGGER.log(LogService.LOG_ERROR, msg); return; } finally { if (toDispose instanceof ISubModuleNode && oldSelectable != null) { ((ISubModuleNode) toDispose).setSelectable(oldSelectable); } } final INavigationContext navigationContext = new NavigationContext(null, toActivateList, toDeactivateList); if (allowsDeactivate(navigationContext)) { if (allowsDispose(navigationContext)) { if (allowsActivate(navigationContext)) { if (nodeStartState != nodeToDispose.getState()) { final String msg = String.format("State of node '%s' changed unexpected!", toDispose); //$NON-NLS-1$ throw new NavigationModelFailure(msg); } deactivate(navigationContext); dispose(navigationContext); final Object target = toDispose.getContext(INavigationNode.CONTEXTKEY_NAVIGATE_AFTER_DISPOSE); if ((target instanceof NavigationNodeId) && (applicationNode != null)) { final NavigationNodeId targetId = (NavigationNodeId) target; navigate(applicationNode, targetId, new NavigationArgument()); } else { activate(navigationContext); } } } } cleanupHistory(nodeToDispose); cleanupJumpTargetListeners(nodeToDispose); } } public void addMarker(final INavigationNode<?> node, final IMarker marker) { if (node != null) { if (node.isActivated() && (marker instanceof DisabledMarker || marker instanceof HiddenMarker)) { final INavigationNode<?> nodeToHide = getNodeToDispose(node); // if ((nodeToHide != null) && (nodeToHide.isVisible() && (nodeToHide.isEnabled()))) { if (nodeToHide != null) { final List<INavigationNode<?>> toDeactivateList = getNodesToDeactivateOnDispose(nodeToHide); final List<INavigationNode<?>> toActivateList = getNodesToActivateOnDispose(nodeToHide); final INavigationContext navigationContext = new NavigationContext(null, toActivateList, toDeactivateList); if (allowsDeactivate(navigationContext) && allowsActivate(navigationContext)) { deactivate(navigationContext); activate(navigationContext); } else { return; } } } if (marker instanceof DisabledMarker || marker instanceof HiddenMarker) { node.setSelected(false); } final List<INavigationNode<?>> toMarkList = new LinkedList<INavigationNode<?>>(); toMarkList.add(node); final INavigationContext navigationContext = new NavigationContext(null, toMarkList, null); addMarker(navigationContext, marker); } } private void addMarker(final INavigationContext context, final IMarker marker) { for (final INavigationNode<?> node : context.getToActivate()) { node.addMarker(context, marker); } } /** * Cleanup the History stacks and removes all occurrences of the node. * * @param toDispose */ private void cleanupHistory(final INavigationNode<?> toDispose) { while (histBack.contains(toDispose)) { histBack.remove(toDispose); } fireBackHistoryChangedEvent(); boolean fhc = false; while (histForward.contains(toDispose)) { histForward.remove(toDispose); fhc = true; } if (fhc) { fireForewardHistoryChangedEvent(); } if (navigationMap.containsKey(toDispose)) { navigationMap.remove(toDispose); } } /** * @since 2.0 */ public INavigationNode<?> create(final INavigationNode<?> sourceNode, final NavigationNodeId targetId) { return provideNode(sourceNode, targetId, null); } /** * @since 2.0 */ public INavigationNode<?> create(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, final NavigationArgument argument) { return provideNode(sourceNode, targetId, argument); } /** * @since 2.0 */ public void move(final INavigationNode<?> sourceNode, final NavigationNodeId targetId) { Assert.isTrue(ModuleNode.class.isAssignableFrom(sourceNode.getClass())); final ModuleNode sourceModuleNode = ModuleNode.class.cast(sourceNode); final INavigationNode<?> targetNode = create(sourceNode, targetId); Assert.isTrue(ModuleGroupNode.class.isAssignableFrom(targetNode.getClass())); final ModuleGroupNode targetModuleGroup = ModuleGroupNode.class.cast(targetNode); final ModuleGroupNode oldParentModuleGroup = ModuleGroupNode.class.cast(sourceModuleNode.getParent()); if (targetModuleGroup.equals(oldParentModuleGroup)) { return; } final boolean isActivated = sourceModuleNode.isActivated(); final boolean isBlocked = sourceModuleNode.isBlocked(); final boolean isEnabled = sourceModuleNode.isEnabled(); final boolean isVisible = sourceModuleNode.isVisible(); sourceModuleNode.dispose(null); sourceModuleNode.deactivate(null); oldParentModuleGroup.removeChild(sourceModuleNode); targetModuleGroup.addChild(sourceModuleNode); for (final ISubModuleNode child : sourceModuleNode.getChildren()) { child.setParent(sourceModuleNode); } sourceModuleNode.setBlocked(isBlocked || targetModuleGroup.isBlocked()); sourceModuleNode.setEnabled(isEnabled && targetModuleGroup.isEnabled()); sourceModuleNode.setVisible(isVisible && targetModuleGroup.isVisible()); if (isActivated) { sourceModuleNode.activate(); } if (oldParentModuleGroup.getChildren().size() == 0) { oldParentModuleGroup.dispose(); } else { oldParentModuleGroup.getChild(0).setSelected(true); } } /** * @since 2.0 */ public INavigationNode<?> navigate(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, final NavigationArgument navigation) { // TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=261832 // if (navigation != null && navigation.isNavigateAsync()) { // navigateAsync(sourceNode, targetId, navigation); // } else { return navigateSync(sourceNode, targetId, navigation, NavigationType.DEFAULT); // } } private enum NavigationType { DEFAULT, JUMP; } private INavigationNode<?> navigateSync(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, final NavigationArgument navigation, final NavigationType navigationType) { INavigationNode<?> targetNode = null; try { targetNode = provideNode(sourceNode, targetId, navigation); } catch (final NavigationModelFailure failure) { LOGGER.log(LogService.LOG_ERROR, failure.getMessage()); } if (targetNode == null) { return null; } final INavigationNode<?> activateNode = targetNode.findNode(targetId); INavigationNode<?> node = activateNode; if (node == null) { node = targetNode; } if (!(sourceNode instanceof IApplicationNode)) { navigationMap.put(node, sourceNode); } if (NavigationType.JUMP == navigationType) { handleJump(sourceNode, node); } node.activate(); try { setFocusOnRidget(node, navigation); } catch (final NavigationModelFailure failure) { LOGGER.log(LogService.LOG_ERROR, failure.getMessage()); } return node; } /* * executes internal jump logic */ private void handleJump(final INavigationNode<?> sourceNode, final INavigationNode<?> node) { runObserved(sourceNode, new Runnable() { public void run() { registerJump(sourceNode, node); } }); } /* * locates the topmost root of the given node */ private INavigationNode<?> getRootNode(final INavigationNode<?> node) { INavigationNode<?> topNode = node; while (topNode.getParent() != null) { topNode = topNode.getParent(); } return topNode; } /* * saves the current jump state for the given node */ private Map<INavigationNode<?>, JumpTargetState> saveJumpState(final INavigationNode<?> node) { final INavigationNode<?> topNode = getRootNode(node); final Map<INavigationNode<?>, JumpTargetState> savedJumpState = new HashMap<INavigationNode<?>, IJumpTargetListener.JumpTargetState>(); saveJumpState(topNode, savedJumpState); return savedJumpState; } /* * saves the current JumpTargetState for all nodes of the subtree starting at the root node recursively */ private void saveJumpState(final INavigationNode<?> root, final Map<INavigationNode<?>, JumpTargetState> savedJumpState) { savedJumpState.put(root, isJumpTarget(root) ? JumpTargetState.ENABLED : JumpTargetState.DISABLED); for (final INavigationNode<?> child : root.getChildren()) { saveJumpState(child, savedJumpState); } } /* * Calculates the JumpTargetState changes for the given nodes and notifies observers */ private void notifyJumpStateChanged(final INavigationNode<?> node, final Map<INavigationNode<?>, JumpTargetState> oldJumpState) { final Map<INavigationNode<?>, JumpTargetState> savedJumpState = saveJumpState(node); final Iterator<Entry<INavigationNode<?>, JumpTargetState>> iterator = savedJumpState.entrySet().iterator(); while (iterator.hasNext()) { final Entry<INavigationNode<?>, JumpTargetState> entry = iterator.next(); if (oldJumpState.get(entry.getKey()) != entry.getValue()) { notifyJumpStateChanged(entry.getKey(), entry.getValue()); } } } /* * Notifies all jumpTargetState observers for the given node about the state change */ private void notifyJumpStateChanged(final INavigationNode<?> node, final JumpTargetState jumpTargetState) { final List<IJumpTargetListener> listeners = jumpTargetListeners.get(node); if (listeners == null) { return; } for (final IJumpTargetListener listener : listeners) { listener.jumpTargetStateChanged(node, jumpTargetState); } } private void registerJump(final INavigationNode<?> source, final INavigationNode<?> target) { // get all sources of the current target Stack<JumpContext> sourceStack = jumpTargets.get(target); if (sourceStack == null) { sourceStack = new Stack<JumpContext>(); jumpTargets.put(target, sourceStack); } // save the source if (sourceStack.size() == 0 || !sourceStack.peek().getSource().equals(source)) { sourceStack.push(new JumpContext(source, target)); } } /* * unregisters the given node as and all of it�s children as jump targets and sources recursively */ private void handleJumpsOnDispose(final INavigationNode<?> node) { runObserved(node, new Runnable() { public void run() { unregisterNodeJumps(node); } }); } private void unregisterNodeJumps(final INavigationNode<?> node) { // as there is one NavigationProcess for the whole tree we need to unregister jump targets, too jumpTargets.remove(node); final Iterator<Map.Entry<INavigationNode<?>, Stack<JumpContext>>> it = jumpTargets.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<INavigationNode<?>, Stack<JumpContext>> entry = it.next(); //clear all occurrences of node as a source final Stack<JumpContext> entryStack = entry.getValue(); JumpContext ctx = null; for (final JumpContext ictx : entryStack) { if (ictx.getSource().equals(node)) { ctx = ictx; } } entryStack.remove(ctx); if (entryStack.isEmpty()) { it.remove(); } } // unregister children for (final INavigationNode<?> child : node.getChildren()) { unregisterNodeJumps(child); } } /** * @since 2.0 */ public void jump(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, final NavigationArgument argument) { navigateSync(sourceNode, targetId, argument, NavigationType.JUMP); } /** * @since 2.0 */ public void jumpBack(final INavigationNode<?> node) { runObserved(node, new Runnable() { public void run() { jumpBackInternal(node); } }); } /** * @since 6.1 */ public INavigationNode<?> getTentativeJumpBackTarget(final INavigationNode<?> node) { final INavigationNode<?> lastJumpedNode = getLastJump(node); // the sourceStack holds all sources (nodes) to the target final Stack<JumpContext> sourceStack = jumpTargets.get(lastJumpedNode); if (sourceStack == null) { return null; } if (!sourceStack.isEmpty()) { return sourceStack.peek().getSource(); } return null; } private void jumpBackInternal(final INavigationNode<?> node) { final INavigationNode<?> lastJumpedNode = getLastJump(node); // the sourceStack holds all sources (nodes) to the target final Stack<JumpContext> sourceStack = jumpTargets.get(lastJumpedNode); if (sourceStack == null) { return; } if (!sourceStack.isEmpty()) { final INavigationNode<?> backTarget = sourceStack.pop().getSource(); setCloseSubTreeOnJumpBack(backTarget); if (sourceStack.isEmpty()) { // remove node as it is no target anymore jumpTargets.remove(lastJumpedNode); } // go back backTarget.activate(); } } /* * executes the runnable collecting changes of the JumpTargetState */ private void runObserved(final INavigationNode<?> node, final Runnable runnable) { final Map<INavigationNode<?>, JumpTargetState> savedJumpState = saveJumpState(node); runnable.run(); notifyJumpStateChanged(node, savedJumpState); } /** * If the node is a child of / or an {@link ModuleGroupNode} locates the latest target node inside the {@link ModuleGroupNode}. Else return the given node * itself. */ private INavigationNode<?> getLastJump(final INavigationNode<?> node) { final ModuleGroupNode moduleGroupNode = (ModuleGroupNode) (node instanceof ModuleGroupNode ? node : node.getParentOfType(ModuleGroupNode.class)); if (moduleGroupNode == null) { return node; } final List<INavigationNode<?>> nodes = new LinkedList<INavigationNode<?>>(); collectNodes(moduleGroupNode, nodes); final List<JumpContext> latestJumpSources = new ArrayList<JumpContext>(); for (final INavigationNode<?> targetNode : nodes) { if (jumpTargets.containsKey(targetNode)) { latestJumpSources.add(jumpTargets.get(targetNode).peek()); } } if (latestJumpSources.size() > 0) { Collections.sort(latestJumpSources); return latestJumpSources.get(latestJumpSources.size() - 1).getTarget(); } return null; } /* * collect all nodes of the the navigation tree with the given root node */ private void collectNodes(final INavigationNode<?> root, final List<INavigationNode<?>> nodes) { nodes.add(root); for (final INavigationNode<?> child : root.getChildren()) { collectNodes(child, nodes); } } private void setCloseSubTreeOnJumpBack(final INavigationNode<?> backTarget) { if (backTarget.isLeaf() && backTarget.getParentOfType(ISubModuleNode.class) != null) { backTarget.getParentOfType(ISubModuleNode.class).setCloseSubTree(true); } else { ((ISubModuleNode) backTarget).setCloseSubTree(true); } } /** * @since 2.0 */ public boolean isJumpTarget(final INavigationNode<?> node) { final ModuleGroupNode moduleGroupNode = node.getParentOfType(ModuleGroupNode.class); INavigationNode<?> targetNode = null; if (moduleGroupNode == null) { targetNode = node; } else { targetNode = getLastJump(moduleGroupNode); } final Stack<JumpContext> sourceStack = jumpTargets.get(targetNode); if (sourceStack != null && sourceStack.size() > 0) { return true; } return false; } /** * @since 2.0 */ public void addJumpTargetListener(final INavigationNode<?> node, final IJumpTargetListener listener) { List<IJumpTargetListener> listeners = jumpTargetListeners.get(node); if (listeners == null) { listeners = new LinkedList<IJumpTargetListener>(); jumpTargetListeners.put(node, listeners); } listeners.add(listener); } /** * @since 2.0 */ public void removeJumpTargetListener(final INavigationNode<?> node, final IJumpTargetListener listener) { final List<IJumpTargetListener> listeners = jumpTargetListeners.get(node); if (listeners == null) { return; } listeners.remove(listener); if (listeners.size() == 0) { jumpTargetListeners.remove(node); } } private void cleanupJumpTargetListeners(final INavigationNode<?> node) { jumpTargetListeners.remove(node); for (final INavigationNode<?> child : node.getChildren()) { cleanupJumpTargetListeners(child); } } /** * Requests focus on given ridget. If the ridget is not found a {@link RuntimeException} is thrown * * @param activateNode * @param navigation */ private void setFocusOnRidget(final INavigationNode<?> activateNode, final NavigationArgument navigation) { if (null != navigation && null != navigation.getRidgetId()) { final IRidgetContainer ridgetContainer = activateNode.getNavigationNodeController().getTypecastedAdapter(IRidgetContainer.class); final IRidget ridget = ridgetContainer.getRidget(navigation.getRidgetId()); if (null != ridget) { ridget.requestFocus(); } else { throw new NavigationModelFailure(String.format("Ridget not found '%s'", navigation.getRidgetId())); //$NON-NLS-1$ } } } // /** // * // * @see org.eclipse.riena.navigation.INavigationProcessor#navigate(org.eclipse.riena.navigation.INavigationNode, // * org.eclipse.riena.navigation.NavigationNodeId, // * org.eclipse.riena.navigation.NavigationArgument) // */ // TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=261832 // private void navigateAsync(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, // // final NavigationArgument navigation) { // // final boolean debug = LOGGER.isLoggable(LogService.LOG_DEBUG); // // if (debug) { // LOGGER.log(LogService.LOG_DEBUG, "async navigation to " + targetId + " started..."); //$NON-NLS-1$ //$NON-NLS-2$ // } // // UIProcess p = new UIProcess("navigate", true, sourceNode) { //$NON-NLS-1$ // private INavigationNode<?> targetNode; // private final long startTime = System.currentTimeMillis(); // // /* // * (non-Javadoc) // * // * @see // * org.eclipse.riena.ui.core.uiprocess.UIProcess#runJob(org.eclipse // * .core.runtime.IProgressMonitor) // */ // @Override // public boolean runJob(IProgressMonitor monitor) { // // targetNode = provideNode(sourceNode, targetId, navigation); // // return true; // } // // private void activateOrFlash(INavigationNode<?> activationNode) { // // if (System.currentTimeMillis() - startTime < 200) { // activationNode.activate(); // } else { // // TODO FLASHING but no activation // } // } // // @Override // public void finalUpdateUI() { // // if (targetNode != null) { // INavigationNode<?> activateNode = targetNode.findNode(targetId); // if (activateNode == null) { // activateNode = targetNode; // } // // navigationMap.put(activateNode, sourceNode); // activateOrFlash(activateNode); // } // // if (debug) { // LOGGER.log(LogService.LOG_DEBUG, "async navigation to " + targetId + " completed"); //$NON-NLS-1$//$NON-NLS-2$ // } // } // // @Override // protected int getTotalWork() { // return 10; // } // }; // // // TODO must be set? // p.setNote("sample uiProcess note"); //$NON-NLS-1$ // p.setTitle("sample uiProcess title"); //$NON-NLS-1$ // p.start(); // } private INavigationNode<?> provideNode(final INavigationNode<?> sourceNode, final NavigationNodeId targetId, final NavigationArgument argument) { try { return getNavigationNodeProvider().provideNode(sourceNode, targetId, argument); } catch (final ExtensionPointFailure failure) { throw new NavigationModelFailure(String.format("Node not found '%s'", targetId), failure); //$NON-NLS-1$ } } protected INavigationNodeProvider getNavigationNodeProvider() { return NavigationNodeProvider.getInstance(); } /** * Ascertain the correct node to dispose. If e.g. the first module in a Group is disposed, then the whole group has to be disposed * * @param toDispose * @return the correct node to dispose */ private INavigationNode<?> getNodeToDispose(final INavigationNode<?> toDispose) { final IModuleNode moduleNode = toDispose.getTypecastedAdapter(IModuleNode.class); if (moduleNode != null) { final INavigationNode<?> parent = moduleNode.getParent(); if (parent instanceof ModuleGroupNode) { final int ind = parent.getIndexOfChild(moduleNode); if (ind == 0) { // first module of a module group return parent; } } } return toDispose; } /** * Find all nodes which have to deactivated and disposed with the passed node. These are all children and its children, in backward order * * @param toDispose * the node to dispose * @return */ private List<INavigationNode<?>> getNodesToDeactivateOnDispose(final INavigationNode<?> toDispose) { final List<INavigationNode<?>> allToDispose = new LinkedList<INavigationNode<?>>(); addAllChildren(allToDispose, toDispose); if (isOnlySubModule(toDispose)) { allToDispose.add(toDispose.getParent()); } return allToDispose; } private boolean isOnlySubModule(final INavigationNode<?> node) { return node.getParent() instanceof IModuleNode && node.getParent().getChildren().size() == 1; } private void addAllChildren(final List<INavigationNode<?>> allToDispose, final INavigationNode<?> toDispose) { for (final Object nextChild : toDispose.getChildren()) { addAllChildren(allToDispose, (INavigationNode<?>) nextChild); } allToDispose.add(toDispose); } /** * Find a node or list of nodes which have to activated when a specified node is disposed * * @param toDispose * the node to dispose * @return a list of nodes */ private List<INavigationNode<?>> getNodesToActivateOnDispose(final INavigationNode<?> toDispose) { // only if the module to dispose was active on dispose, // something else has to be activated // we assume, that if any submodule of this module was active, // than this module is active itself // while dispose of a node, the next brother node must be activated if (toDispose.isActivated()) { final INavigationNode<?> parentOfToDispose = getParentToActivate(toDispose); if (parentOfToDispose != null) { final List<?> childrenOfParentOfToDispose = parentOfToDispose.getChildren(); final List<INavigationNode<?>> activatableNode = getActivatableNodes(childrenOfParentOfToDispose); if (childrenOfParentOfToDispose.size() > 1) { // there must be a least 2 children: // the disposed will be removed from the list // get the first child which is not the one to remove for (final INavigationNode<?> nextChild : activatableNode) { if (!nextChild.equals(toDispose)) { if (isSelectable(nextChild)) { return getNodesToActivateOnActivation(nextChild); } else { final INavigationNode<?> selectableChild = getSelectableChild(nextChild); if (selectableChild != null) { return getNodesToActivateOnActivation(selectableChild); } continue; } } } if (!(parentOfToDispose instanceof ISubModuleNode)) { throw new NavigationModelFailure("No sibling of node can be selected!"); //$NON-NLS-1$ } } if (parentOfToDispose instanceof ISubModuleNode) { final INavigationNode<?> selectable = getSelectableParent(parentOfToDispose); if (selectable != null) { return getNodesToActivateOnActivation(selectable); } else { throw new NavigationModelFailure("No parent of node can be selected!"); //$NON-NLS-1$ } } } } return new LinkedList<INavigationNode<?>>(); } private INavigationNode<?> getSelectableParent(final INavigationNode<?> node) { if (node == null) { return null; } if (isSelectable(node)) { return node; } final INavigationNode<?> parent = getSelectableParent(node.getParent()); if (getChildToActivate(parent) == null) { return null; } return parent; } private INavigationNode<?> getSelectableChild(final INavigationNode<?> node) { if (node == null) { return null; } for (final INavigationNode<?> child : node.getChildren()) { if (isSelectable(child)) { return child; } } for (final INavigationNode<?> child : node.getChildren()) { final INavigationNode<?> selectableChild = getSelectableChild(child); if (selectableChild != null) { return selectableChild; } } return null; } private boolean isSelectable(final INavigationNode<?> node) { if (!node.isVisible() || !node.isEnabled()) { return false; } if (node instanceof ISubModuleNode) { return ((ISubModuleNode) node).isSelectable(); } return true; } private INavigationNode<?> getParentToActivate(final INavigationNode<?> node) { if (isOnlySubModule(node)) { if (node.getParentOfType(IModuleGroupNode.class).getChildren().size() == 1) { return node.getParentOfType(ISubApplicationNode.class); } return node.getParentOfType(IModuleGroupNode.class); } return node.getParent(); } /** * Removes all not activatable nodes (e.g. hidden nodes) from the given list. * * @param nodes * list of node * @return filtered list */ private List<INavigationNode<?>> getActivatableNodes(final List<?> nodes) { final List<INavigationNode<?>> activatableNodes = new LinkedList<INavigationNode<?>>(); for (final Object node : nodes) { if (node instanceof INavigationNode<?>) { final INavigationNode<?> naviNode = (INavigationNode<?>) node; if (naviNode.isVisible() && naviNode.isEnabled()) { activatableNodes.add(naviNode); } } } return activatableNodes; } /** * Finds all the nodes to activate * * @param toActivate * the node do activate * @return a List of all nodes to activate */ private List<INavigationNode<?>> getNodesToActivateOnActivation(final INavigationNode<?> toActivate) { final List<INavigationNode<?>> nodesToActivate = new LinkedList<INavigationNode<?>>(); // go up and add all parents addParentsToActivate(nodesToActivate, toActivate); // go down and add all children addChildrenToActivate(nodesToActivate, toActivate); // the first element in the list must be the last not active parent return nodesToActivate; } private void addParentsToActivate(final List<INavigationNode<?>> nodesToActivate, final INavigationNode<?> toActivate) { // go up to the next active parent final INavigationNode<?> parent = getActivationParent(toActivate); if (parent != null) { if (parent.isActivated()) { addSelectableNode(nodesToActivate, toActivate); } else { addParentsToActivate(nodesToActivate, parent); addSelectableNode(nodesToActivate, toActivate); } } else { nodesToActivate.add(toActivate); } } private void addSelectableNode(final List<INavigationNode<?>> nodesToActivate, final INavigationNode<?> toActivate) { if (toActivate instanceof ISubModuleNode && !((ISubModuleNode) toActivate).isSelectable()) { Nop.reason("do not add; not selectable"); //$NON-NLS-1$ } else { nodesToActivate.add(toActivate); } } private INavigationNode<?> getActivationParent(final INavigationNode<?> child) { // by a subModule is it the module node // by all other die direct parent final ISubModuleNode subModuleNode = child.getTypecastedAdapter(ISubModuleNode.class); if (subModuleNode != null) { INavigationNode<?> parent = child.getParent(); while (parent != null) { if (parent.getTypecastedAdapter(IModuleNode.class) != null) { return parent; } else { parent = parent.getParent(); } } } else { return child.getParent(); } return null; } private void addChildrenToActivate(final List<INavigationNode<?>> nodesToActivate, final INavigationNode<?> toActivate) { final INavigationNode<?> childToActivate = getChildToActivate(toActivate); if (childToActivate != null) { nodesToActivate.add(childToActivate); addChildrenToActivate(nodesToActivate, childToActivate); } } private List<INavigationNode<?>> getNodesToDeactivateOnActivation(final INavigationNode<?> toActivate) { // get next active parent or root final List<INavigationNode<?>> nodesToDeactivate = new LinkedList<INavigationNode<?>>(); final INavigationNode<?> activeParent = getNextActiveParent(toActivate); if (activeParent != null) { // Handle the case that no active child is available due // to some errors or exceptions which occurred before final INavigationNode<?> activeChild = getActiveChild(activeParent); if (activeChild != null) { addChildrenToDeactivate(nodesToDeactivate, activeChild); } } else { // no one was active before addChildrenToDeactivate(nodesToDeactivate, getTopParent(toActivate)); } return nodesToDeactivate; } private void addChildrenToDeactivate(final List<INavigationNode<?>> nodesToDeactivate, final INavigationNode<?> toAdd) { final INavigationNode<?> activeChild = getActiveChild(toAdd); if (activeChild != null) { addChildrenToDeactivate(nodesToDeactivate, activeChild); nodesToDeactivate.add(toAdd); } else { nodesToDeactivate.add(toAdd); } } /** * find the next active parent */ private INavigationNode<?> getNextActiveParent(final INavigationNode<?> node) { // the next active parent must be at least a Module Node final ISubModuleNode subModuleNode = node.getTypecastedAdapter(ISubModuleNode.class); if (subModuleNode != null) { // if sub module node go up return getNextActiveParent(subModuleNode.getParent()); } else if (node.isActivated()) { // it is not a sub module node and is activated return node; } else if (node.getParent() != null) { // it is not a sub module and is not active return getNextActiveParent(node.getParent()); } else { return null; } } /** * find the top parent */ private INavigationNode<?> getTopParent(final INavigationNode<?> node) { if (node.getParent() != null) { return getTopParent(node.getParent()); } else { return node; } } private boolean allowsActivate(final INavigationContext context) { for (final INavigationNode<?> nextToActivate : context.getToActivate()) { if (!nextToActivate.allowsActivate(context)) { return false; } } return true; } private boolean allowsDispose(final INavigationContext context) { for (final INavigationNode<?> nextToDeactivate : context.getToDeactivate()) { if (!nextToDeactivate.allowsDispose(context)) { return false; } } return true; } private boolean allowsDeactivate(final INavigationContext context) { for (final INavigationNode<?> nextToDeactivate : context.getToDeactivate()) { if (!nextToDeactivate.allowsDeactivate(context)) { return false; } } return true; } private void activate(final INavigationContext context) { Assert.isNotNull(context); Assert.isNotNull(context.getToActivate()); final List<INavigationNode<?>> nextNodesToActivate = context.getToActivate(); if (nextNodesToActivate.isEmpty()) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - There is no node to activate!"); //$NON-NLS-1$ } for (final INavigationNode<?> nextToActivate : nextNodesToActivate) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - beforeActivate: " + nextToActivate.getNodeId()); //$NON-NLS-1$ } nextToActivate.onBeforeActivate(context); } for (final INavigationNode<?> nextToActivate : nextNodesToActivate) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - activate: " + nextToActivate.getNodeId()); //$NON-NLS-1$ } nextToActivate.activate(context); setAsSelectedChild(nextToActivate); } for (final INavigationNode<?> nextToActivate : Iter.ableReverse(nextNodesToActivate)) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - onAfterActivate: " + nextToActivate.getNodeId()); //$NON-NLS-1$ } nextToActivate.onAfterActivate(context); } if (!nextNodesToActivate.isEmpty()) { checkActiveNodes(nextNodesToActivate.iterator().next()); } } /** * Checks if only one kind of navigation node (e.g. ISubModuleNode) is activated. * * @param someChild * some child in the tree of the navigation model * @return {@code true} all active nodes are correct, otherwise {@code false} */ private boolean checkActiveNodes(final INavigationNode<?> someChild) { boolean ok = true; if (someChild instanceof IApplicationNode) { return ok; } final IApplicationNode application = someChild.getParentOfType(IApplicationNode.class); final List<INavigationNode<?>> activeNodes = new LinkedList<INavigationNode<?>>(); addChildren(application, activeNodes, State.ACTIVATED); for (int i = 0; i < activeNodes.size(); i++) { final INavigationNode<?> iNode = activeNodes.get(i); for (int j = i + 1; j < activeNodes.size(); j++) { final INavigationNode<?> jNode = activeNodes.get(j); if (iNode instanceof IApplicationNode && jNode instanceof IApplicationNode) { LOGGER.log(LogService.LOG_ERROR, "Two active IApplicationNodes: " + iNode.getNodeId() + ", " + jNode.getNodeId()); //$NON-NLS-1$ //$NON-NLS-2$ ok = false; } else if (iNode instanceof ISubApplicationNode && jNode instanceof ISubApplicationNode) { LOGGER.log(LogService.LOG_ERROR, "Two active ISubApplicationNodes: " + iNode.getNodeId() + ", " + jNode.getNodeId()); //$NON-NLS-1$ //$NON-NLS-2$ ok = false; } else if (iNode instanceof IModuleGroupNode && jNode instanceof IModuleGroupNode) { LOGGER.log(LogService.LOG_ERROR, "Two active IModuleGroupNode: " + iNode.getNodeId() + ", " + jNode.getNodeId()); //$NON-NLS-1$ //$NON-NLS-2$ ok = false; } else if (iNode instanceof IModuleNode && jNode instanceof IModuleNode) { LOGGER.log(LogService.LOG_ERROR, "Two active IModuleNode: " + iNode.getNodeId() + ", " + jNode.getNodeId()); //$NON-NLS-1$ //$NON-NLS-2$ ok = false; } else if (iNode instanceof ISubModuleNode && jNode instanceof ISubModuleNode) { LOGGER.log(LogService.LOG_ERROR, "Two active ISubModuleNodes: " + iNode.getNodeId() + ", " + jNode.getNodeId()); //$NON-NLS-1$ //$NON-NLS-2$ ok = false; } } } return ok; } private void addChildren(final INavigationNode<?> parent, final List<INavigationNode<?>> nodes, final State nodeState) { if (parent != null) { for (final INavigationNode<?> child : parent.getChildren()) { if (child.getState() == nodeState) { nodes.add(child); } addChildren(child, nodes, nodeState); } } } private void deactivate(final INavigationContext context) { final Collection<INavigationNode<?>> previouslyActivatedNodes = new ArrayList<INavigationNode<?>>(); for (final INavigationNode<?> nextToDeactivate : Iter.ableReverse(context.getToDeactivate())) { if (nextToDeactivate.isActivated()) { previouslyActivatedNodes.add(nextToDeactivate); if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - beforeDeactivate: " + nextToDeactivate.getNodeId()); //$NON-NLS-1$ } nextToDeactivate.onBeforeDeactivate(context); } } for (final INavigationNode<?> nextToDeactivate : context.getToDeactivate()) { // check for activated to make this method usable for disposing if (previouslyActivatedNodes.contains(nextToDeactivate)) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - deactivate: " + nextToDeactivate.getNodeId()); //$NON-NLS-1$ } nextToDeactivate.deactivate(context); } } for (final INavigationNode<?> nextToDeactivate : context.getToDeactivate()) { if (previouslyActivatedNodes.contains(nextToDeactivate)) { if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - onAfterDeactivate: " + nextToDeactivate.getNodeId()); //$NON-NLS-1$ } nextToDeactivate.onAfterDeactivate(context); } } } private void dispose(final INavigationContext context) { for (final INavigationNode<?> nextToDispose : Iter.ableReverse(context.getToDeactivate())) { nextToDispose.onBeforeDispose(context); } for (final INavigationNode<?> nextToDispose : context.getToDeactivate()) { // check for activated to make this method usable for disposing if (DEBUG_NAVIGATION_PROCESSOR) { LOGGER.log(LogService.LOG_DEBUG, "NaviProc: - dispos: " + nextToDispose.getNodeId()); //$NON-NLS-1$ } nextToDispose.dispose(context); // remove the node from tree final INavigationNode<?> parent = nextToDispose.getParent(); if (parent != null) { parent.removeChild(nextToDispose); } } for (final INavigationNode<?> nextToDispose : context.getToDeactivate()) { nextToDispose.onAfterDispose(context); // clean up history stacks cleanupHistory(nextToDispose); } } /** * Context with lists of nodes to be prepared, activated and deactivated. */ private static class NavigationContext implements INavigationContext { private final static List<INavigationNode<?>> EMPTY_LIST = new LinkedList<INavigationNode<?>>(); private List<INavigationNode<?>> toDeactivate; private List<INavigationNode<?>> toActivate; private List<INavigationNode<?>> toPrepare; /** * Creates a context with nodes to be prepared, activated and deactivated. * * @param toPrepare * nodes to be prepared * @param toActivate * nodes to be activated * @param toDeactivate * nodes to be deactevated */ public NavigationContext(final List<INavigationNode<?>> toPrepare, final List<INavigationNode<?>> toActivate, final List<INavigationNode<?>> toDeactivate) { super(); this.toPrepare = toPrepare; this.toActivate = toActivate; this.toDeactivate = toDeactivate; if (this.toPrepare == null) { this.toPrepare = EMPTY_LIST; } if (this.toActivate == null) { this.toActivate = EMPTY_LIST; } if (this.toDeactivate == null) { this.toDeactivate = EMPTY_LIST; } } public List<INavigationNode<?>> getToActivate() { return toActivate; } public List<INavigationNode<?>> getToDeactivate() { return toDeactivate; } public List<INavigationNode<?>> getToPrepare() { return toPrepare; } } /** * The navigation processor decides which child to activate even initially * * @param pNode * the node who's child is searched */ private INavigationNode<?> getChildToActivate(final INavigationNode<?> pNode) { // for sub module is always null final ISubModuleNode subModuleNode = pNode.getTypecastedAdapter(ISubModuleNode.class); if (subModuleNode != null) { if (!subModuleNode.isSelectable()) { if (!subModuleNode.getChildren().isEmpty()) { return findSelectableChildNode(subModuleNode); } } return null; } // for module node is it the deepest selected sub Module node final IModuleNode moduleNode = pNode.getTypecastedAdapter(IModuleNode.class); if (moduleNode != null) { ISubModuleNode nextChild = getSelectedChild(moduleNode); ISubModuleNode selectedChild = null; while (nextChild != null) { selectedChild = nextChild.isSelectable() ? nextChild : selectedChild; nextChild = getSelectedChild(nextChild); } //Return the last Module node which is active and can be selected if (selectedChild != null) { return selectedChild; } if (moduleNode.getChildren().size() > 0) { // find the first selectable node in the list of childs ISubModuleNode subModule = null; final Iterator<ISubModuleNode> subIter = moduleNode.getChildren().iterator(); while (subModule == null && subIter.hasNext()) { subModule = findSelectableChildNode(subIter.next()); } return subModule; } else { return null; } } // for all others is it the direct selected child final INavigationNode<?> nextSelectedChild = getSelectedChild(pNode); if (nextSelectedChild != null) { return nextSelectedChild; } else { for (final INavigationNode<?> next : pNode.getChildren()) { if (next.isVisible() && next.isEnabled()) { return next; } } return null; } } private ISubModuleNode findSelectableChildNode(final ISubModuleNode startNode) { // if node is not visible or not enabled, return null (note: this method is recursive, see below) if (!startNode.isVisible() || !startNode.isEnabled()) { return null; } // selectable node found if (startNode.isSelectable()) { return startNode; } // if not selectable, expand it because it does not get activated some else need to expand it startNode.setExpanded(true); // check childs for selectable node for (final ISubModuleNode child : startNode.getChildren()) { final ISubModuleNode found = findSelectableChildNode(child); if (found != null) { return found; } } return null; } private INavigationNode<?> getActiveChild(final INavigationNode<?> pNode) { // for a Sub Module it is always null final ISubModuleNode subModuleNode = pNode.getTypecastedAdapter(ISubModuleNode.class); if (subModuleNode != null) { return null; } // for a module node it is the last selected child final IModuleNode moduleNode = pNode.getTypecastedAdapter(IModuleNode.class); if (moduleNode != null) { ISubModuleNode nextChild = getSelectedChild(moduleNode); if (nextChild != null) { while (nextChild != null) { if (nextChild.isActivated()) { return nextChild; } else if (getSelectedChild(nextChild) != null) { nextChild = getSelectedChild(nextChild); } else { return null; } } } else { return null; } } // for all other the selectedChild if any and active final INavigationNode<?> nextSelectedChild = getSelectedChild(pNode); if (nextSelectedChild != null && nextSelectedChild.isActivated()) { return nextSelectedChild; } else { return null; } } /** * since the navigation processor decides, who is the next child to activate: so the processor can jump over nodes and decides which nodes are deactivated, * the navigation processor also must set the selected chain. While activation the navigation processor works with the selected chain to find which nodes * have to be activated an which to be deactivated. The selected chain must always show the way from the to element to the active one * * @param pNode * the node to set the selected chain for. */ private void setAsSelectedChild(final INavigationNode<?> pNode) { // when activating a sub module the chain must be set up to the module // node final ISubModuleNode subModuleNode = pNode.getTypecastedAdapter(ISubModuleNode.class); if (subModuleNode != null) { ISubModuleNode nextToSet = subModuleNode; while (nextToSet != null) { if (nextToSet.getParent() != null) { setSelectedChild(nextToSet.getParent(), nextToSet); nextToSet = nextToSet.getParent().getTypecastedAdapter(ISubModuleNode.class); } } // when a sub module node is activated it sets its own selected // child to null to break the selected chain setSelectedChild(subModuleNode, null); return; } // for all others only to the next parent if (pNode.getParent() != null) { setSelectedChild(pNode.getParent(), pNode); } } /** * this navigation processor allows only one selected child, so it resets the flag in all children before marking the one * * @param parent * the parent to reset in * @param child * the child to set as selected */ private void setSelectedChild(final INavigationNode<?> parent, final INavigationNode<?> child) { for (final INavigationNode<?> next : parent.getChildren()) { if (next.equals(child)) { next.setSelected(true); if (next.isActivated()) {//remember only active subModule nodes //We have a new selected child. Remember in history. buildHistory(next); } } else { next.setSelected(false); } } } private INavigationNode<?> getSelectedChild(final INavigationNode<?> pNavigationNode) { for (final INavigationNode<?> next : pNavigationNode.getChildren()) { if (next.isSelected()) { return next; } } return null; } private ISubModuleNode getSelectedChild(final IModuleNode pModuleNode) { for (final ISubModuleNode next : pModuleNode.getChildren()) { if (next.isSelected()) { return next; } } return null; } private ISubModuleNode getSelectedChild(final ISubModuleNode pSubModuleNode) { for (final ISubModuleNode next : pSubModuleNode.getChildren()) { if (next.isSelected()) { return next; } } return null; } public void historyBack() { if (getHistoryBackSize() > 0) { final INavigationNode<?> current = histBack.pop();// skip self fireBackHistoryChangedEvent(); histForward.push(current); if (histForward.size() > MAX_HISTORY_LENGTH) { histForward.remove(histForward.firstElement()); } fireForewardHistoryChangedEvent(); final INavigationNode<?> node = histBack.peek();// activate parent if (node != null) { activate(node); } } } /** * Fires a INavigationHistoryEvent when the backward history changes. */ private void fireBackHistoryChangedEvent() { if (navigationListener.size() == 0) { return; } final INavigationHistoryEvent event = new NavigationHistoryEvent(histBack.subList(0, Math.max(0, histBack.size() - 1))); for (final INavigationHistoryListener listener : navigationListener) { listener.backHistoryChanged(event); } } public void historyForward() { if (getHistoryForwardSize() > 0) { final INavigationNode<?> current = histForward.pop(); fireForewardHistoryChangedEvent(); if (current != null) { histBack.push(current); if (histBack.size() > MAX_HISTORY_LENGTH) { histBack.remove(histBack.firstElement()); } activate(current); fireBackHistoryChangedEvent(); } } } /** * Fires a INavigationHistoryEvent when the forward history changes. */ private void fireForewardHistoryChangedEvent() { if (navigationListener.size() == 0) { return; } final INavigationHistoryEvent event = new NavigationHistoryEvent(histForward.subList(0, histForward.size())); for (final INavigationHistoryListener listener : navigationListener) { listener.forwardHistoryChanged(event); } } /** * Answer the current size of the next navigation history * * @see org.eclipse.riena.navigation.INavigationNode#getHistorySize() * @return the amount of navigation nodes on the navigation stack */ public int getHistoryBackSize() { return histBack.size() - 1; } /** * Answers the currently selected navigation node in the NavigationTree * * @return the currently selected SubModuleNode in the NavigationTree or null */ public INavigationNode<?> getSelectedNode() { //always the top most histback item is the currently selected return histBack.peek(); } /** * Answer the current size of the previous navigation history * * @see org.eclipse.riena.navigation.INavigationNode#getHistorySize() * @return the amount of navigation nodes on the navigation stack */ public int getHistoryForwardSize() { return histForward.size(); } /** * Navigates to the caller (the source node) of the given targetNode. If there is no previous caller, no navigation is performed. If the targetNode itself * has no caller in the navigationMap, the tree hierarchy is searched up to the tree root. * * @param targetNode * The node where we have navigate to and return from */ public void navigateBack(final INavigationNode<?> targetNode) { INavigationNode<?> sourceNode = null; INavigationNode<?> lookupNode = targetNode; while (sourceNode == null) { sourceNode = navigationMap.get(lookupNode); if (sourceNode == null) { lookupNode = lookupNode.getParent(); if (lookupNode == null) { return; } } } navigate(targetNode, sourceNode.getNodeId(), null); } public synchronized void addNavigationHistoryListener(final INavigationHistoryListener listener) { if (!navigationListener.contains(listener)) { navigationListener.add(listener); INavigationHistoryEvent event = new NavigationHistoryEvent(histBack.subList(0, (histBack.size() > 0 ? histBack.size() - 1 : 0))); listener.backHistoryChanged(event); event = new NavigationHistoryEvent(histForward.subList(0, histForward.size())); listener.forwardHistoryChanged(event); } } public synchronized void removeNavigationHistoryListener(final INavigationHistoryListener listener) { navigationListener.remove(listener); } /** * @since 3.0 */ public List<INavigationNode<?>> getHistory() { return Collections.unmodifiableList(histBack); } }