/******************************************************************************* * 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.ui.swt.views; import java.util.List; import org.eclipse.riena.core.marker.IMarker; import org.eclipse.riena.navigation.IModuleGroupNode; import org.eclipse.riena.navigation.IModuleNode; import org.eclipse.riena.navigation.INavigationNode; import org.eclipse.riena.navigation.ISubModuleNode; import org.eclipse.riena.navigation.listener.ModuleGroupNodeListener; import org.eclipse.riena.navigation.listener.ModuleNodeListener; import org.eclipse.riena.navigation.listener.NavigationTreeObserver; import org.eclipse.riena.navigation.listener.SubModuleNodeListener; import org.eclipse.riena.navigation.model.SubModuleNode; import org.eclipse.riena.navigation.ui.controllers.ModuleController; import org.eclipse.riena.ui.core.marker.DisabledMarker; import org.eclipse.riena.ui.ridgets.ISelectableRidget; import org.eclipse.riena.ui.ridgets.ITreeRidget; import org.eclipse.riena.ui.ridgets.tree2.ITreeNode; import org.eclipse.riena.ui.swt.lnf.LnfKeyConstants; import org.eclipse.riena.ui.swt.lnf.LnfManager; import org.eclipse.riena.ui.swt.lnf.rienadefault.RienaDefaultLnf; import org.eclipse.riena.ui.swt.uiprocess.SwtUISynchronizer; /** * Controller of a module with a tree. */ public class SWTModuleController extends ModuleController { private ITreeRidget tree; private final static String PROPERTY_ENABLED = "enabled"; //$NON-NLS-1$ private final static String PROPERTY_VISIBLE = "visible"; //$NON-NLS-1$ private final static String PROPERTY_IMAGE = "icon"; //$NON-NLS-1$ private final static String PROPERTY_EXPANDED = "expanded"; //$NON-NLS-1$ private final boolean showOneSubTree; private NavigationTreeObserver navigationTreeObserver; private ModuleGroupListener moduleGrouplistener; private IModuleGroupNode parentModuleGroup; /** * @param navigationNode */ public SWTModuleController(final IModuleNode navigationNode) { super(navigationNode); addListeners(); final RienaDefaultLnf lnf = LnfManager.getLnf(); showOneSubTree = lnf.getBooleanSetting(LnfKeyConstants.SUB_MODULE_TREE_SHOW_ONE_SUB_TREE, false); } /** * @param tree * the tree to set */ public void setTree(final ITreeRidget tree) { this.tree = tree; } /** * @return the tree */ public ITreeRidget getTree() { return tree; } @Override public void afterBind() { super.afterBind(); updateNavigationNodeMarkers(); bindTree(); } // helping methods ////////////////// /** * Adds listeners for sub-module and module nodes. */ private void addListeners() { navigationTreeObserver = new NavigationTreeObserver(); navigationTreeObserver.addListener(new ModuleListener()); navigationTreeObserver.addListener(new SubModuleListener()); navigationTreeObserver.addListenerTo(getNavigationNode()); } /** * Binds the tree to a selection model and tree model. */ private void bindTree() { tree.setRootsVisible(false); final INavigationNode<?>[] roots = createTreeRootNodes(); tree.bindToModel(roots, SubModuleNode.class, ITreeNode.PROPERTY_CHILDREN, ITreeNode.PROPERTY_PARENT, "label", PROPERTY_ENABLED, PROPERTY_VISIBLE, PROPERTY_IMAGE, null, PROPERTY_EXPANDED); //$NON-NLS-1$ tree.setSelectionType(ISelectableRidget.SelectionType.SINGLE); selectActiveNode(); tree.updateFromModel(); } /** * Creates the list of the root nodes of the tree. * <p> * This method returns only one root, the module node. So dynamically * sub-module can be added directly below the module node and they are * displayed in the tree (without generating and binding a new model with * new root nodes.) * * @return root nodes */ private IModuleNode[] createTreeRootNodes() { final IModuleNode moduleNode = getNavigationNode(); return new IModuleNode[] { moduleNode }; } private void runAsync(final Runnable op) { new SwtUISynchronizer().asyncExec(op); } /** * Selects the active sub-module of this module in the tree. */ private void selectActiveNode() { setSelectedNode(getNavigationNode()); } /** * Ensures that only the sub-tree with the given node is expanded; all other * trees are collapsed. * * @param activeNode * active sub-module node */ private void showOneSubTree(final ISubModuleNode activeNode) { if (isShowOneSubTree()) { collapseSibling(activeNode); getNavigationNode().getNavigationProcessor().markNodesToCollapse(activeNode); // if (!activeNode.isExpanded()) { // activeNode.setExpanded(true); // } } } /** * Collapses all sibling nodes; also in upper levels. Only nodes that are * marked as closeSubTree == true are being collapsed automatically. * * @param node * sub-module node the active node */ private void collapseSibling(final ISubModuleNode node) { final INavigationNode<?> parent = node.getParent(); if (parent == null) { return; } for (final INavigationNode<?> sibling : parent.getChildren()) { if (sibling instanceof ISubModuleNode) { final ISubModuleNode siblingSubModuleNode = (ISubModuleNode) sibling; if (siblingSubModuleNode != node && siblingSubModuleNode.isCloseSubTree() && siblingSubModuleNode.isExpanded()) { siblingSubModuleNode.setExpanded(false); } siblingSubModuleNode.setCloseSubTree(false); if (siblingSubModuleNode != node && !siblingSubModuleNode.isLeaf()) { collapseChildren(siblingSubModuleNode); } if (parent instanceof ISubModuleNode) { collapseSibling((ISubModuleNode) parent); if (node != siblingSubModuleNode) { collapseChildren(siblingSubModuleNode); } } } } } private void collapseChildren(final ISubModuleNode node) { final List<ISubModuleNode> childen = node.getChildren(); for (final ISubModuleNode child : childen) { if (child.isCloseSubTree() && (child.isExpanded())) { child.setExpanded(false); } child.setCloseSubTree(false); collapseChildren(child); } } /** * Selects the active sub-module in the tree. * * @param node */ private void setSelectedNode(final INavigationNode<?> node) { if (node.isActivated() && (node != getNavigationNode())) { tree.setSelection(node); expandAllParents(node); } for (final INavigationNode<?> child : node.getChildren()) { setSelectedNode(child); } } private void expandAllParents(final INavigationNode<?> node) { INavigationNode<?> parent = node.getParent(); while (parent instanceof SubModuleNode) { tree.expand(parent); parent = parent.getParent(); } } /** * Returns whether only one sub-tree inside the module is expanded at the * same time. * * @return {@code true} only one sub-tree can be expanded; {@code false} * more than one sub-tree can be expanded */ private boolean isShowOneSubTree() { return showOneSubTree; } // helping classes ////////////////// /** * Updates the tree if a sub-module node is added or remove form parent * sub-module node. */ private class SubModuleListener extends SubModuleNodeListener { @Override public void afterActivated(final ISubModuleNode source) { // if activation was changed programmatically, we need to select // the activated node (i.e. undo / redo navigation). Since other // activation changes may already be on the event queue, we have // to do this asynchronously (i.e. put this into the end of the // queue), to preserve the ordering runAsync(new Runnable() { public void run() { selectActiveNode(); showOneSubTree(source); } }); } @Override public void expandedChanged(final ISubModuleNode source) { super.expandedChanged(source); if (tree != null) { if (source.isExpanded()) { expandSubModuleNode(source); } else { tree.collapse(source); } } } protected void expandSubModuleNode(final ISubModuleNode source) { expandTree(source); final List<ISubModuleNode> children = source.getChildren(); if (children.size() > 0) { for (final ISubModuleNode child : children) { if (child.isExpanded()) { expandSubModuleNode(child); } } } } @Override public void childRemoved(final ISubModuleNode source, final ISubModuleNode childRemoved) { super.childRemoved(source, childRemoved); if (tree != null) { if (source.getChildren().size() == 0) { tree.collapse(source); return; } updateTree(childRemoved); } } @Override public void childAdded(final ISubModuleNode source, final ISubModuleNode childAdded) { super.childAdded(source, childAdded); if (source.getIndexOfChild(childAdded) < source.getChildren().size() - 1) { tree.updateFromModel(); } else { updateTree(childAdded); } // If a leaf is now a folder // the expansion must be updated for the tree item if (source.getChildren().size() == 1) { if (source.isExpanded()) { tree.expand(source); } } } } /** * @since 3.0 */ protected void expandTree(final ISubModuleNode source) { tree.expand(source); } /** * updates the tree whenever submodule are added */ private class ModuleListener extends ModuleNodeListener { @Override public void childAdded(final IModuleNode source, final ISubModuleNode childAdded) { super.childAdded(source, childAdded); if (source.getIndexOfChild(childAdded) < source.getChildren().size() - 1) { tree.updateFromModel(); return; } updateTree(childAdded); } @Override public void presentationChanged(final IModuleNode source) { if (!getNavigationNode().getNavigationNodeController().equals(SWTModuleController.this)) { navigationTreeObserver.removeListenerFrom(getNavigationNode()); } } /** * {@inheritDoc} * <p> * If a new parent has set, a listener for the module group is added. If * no parent is set ({@code module==null}), the listener for the module * group is removed. */ @Override public void parentChanged(final IModuleNode module) { super.parentChanged(module); if (module.getParent() instanceof IModuleGroupNode) { if (moduleGrouplistener == null) { moduleGrouplistener = new ModuleGroupListener(); } parentModuleGroup = (IModuleGroupNode) module.getParent(); parentModuleGroup.addListener(moduleGrouplistener); } else { if ((parentModuleGroup != null) && (moduleGrouplistener != null)) { parentModuleGroup.removeListener(moduleGrouplistener); parentModuleGroup = null; } } } } private class ModuleGroupListener extends ModuleGroupNodeListener { /** * {@inheritDoc} * <p> * If a DisabledMarer was added or removed, the sub-module will be * updated. */ @Override public void markerChanged(final IModuleGroupNode source, final IMarker marker) { super.markerChanged(source, marker); if (marker instanceof DisabledMarker) { tree.updateFromModel(); } } } private void updateTree(final ISubModuleNode source) { if (tree == null || !tree.isVisible()) { return; } if (source == null || !source.isVisible()) { return; } final IModuleNode moduleNode = source.getParentOfType(IModuleNode.class); if (moduleNode != null && !moduleNode.isActivated()) { return; } tree.updateFromModel(); } }