package org.ovirt.engine.ui.webadmin.widget.tree; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.ovirt.engine.ui.common.idhandler.ElementIdHandler; import org.ovirt.engine.ui.common.widget.action.AbstractActionStackPanelItem; import org.ovirt.engine.ui.common.widget.action.SimpleActionPanel; import org.ovirt.engine.ui.common.widget.tree.ElementIdCellTree; import org.ovirt.engine.ui.uicommonweb.BaseCommandTarget; import org.ovirt.engine.ui.uicommonweb.UICommand; import org.ovirt.engine.ui.uicommonweb.models.SystemTreeItemModel; import org.ovirt.engine.ui.uicommonweb.models.SystemTreeModel; import org.ovirt.engine.ui.webadmin.ApplicationConstants; import org.ovirt.engine.ui.webadmin.gin.AssetProvider; import org.ovirt.engine.ui.webadmin.gin.ClientGinjectorProvider; import org.ovirt.engine.ui.webadmin.uicommon.model.SystemTreeModelProvider; import org.ovirt.engine.ui.webadmin.widget.action.WebAdminButtonDefinition; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.CellTree; import com.google.gwt.user.cellview.client.CellTree.Style; import com.google.gwt.user.cellview.client.TreeNode; import com.google.gwt.user.client.ui.Widget; public class SystemTree extends AbstractActionStackPanelItem<SystemTreeModelProvider, SystemTreeModel, CellTree> { private static final SystemTreeResources res = GWT.create(SystemTreeResources.class); private static final int ALL_LEVELS = Integer.MAX_VALUE; private static final int ITEM_LEVEL = 3; /** * This map will store the current state of the system tree right before it is redrawn. When redrawing a tree * the state is reset and the current state is lost. This map will be populated right before the underlying * model is updated which causes a redraw to happen. Then after the redraw we can use the map to restore the * state of the tree to what it was before the redraw. */ protected final Map<Object, Map<String, Boolean>> nodeStateMap = new HashMap<>(); interface WidgetUiBinder extends UiBinder<Widget, SystemTree> { WidgetUiBinder uiBinder = GWT.create(WidgetUiBinder.class); } interface WidgetIdHandler extends ElementIdHandler<SystemTree> { WidgetIdHandler idHandler = GWT.create(WidgetIdHandler.class); } private static final ApplicationConstants constants = AssetProvider.getConstants(); public SystemTree(SystemTreeModelProvider modelProvider) { super(modelProvider); initWidget(WidgetUiBinder.uiBinder.createAndBindUi(this)); WidgetIdHandler.idHandler.generateAndSetIds(this); addActionButtons(modelProvider); addModelListeners(modelProvider); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected SimpleActionPanel<SystemTreeModel> createActionPanel(SystemTreeModelProvider modelProvider) { return new SimpleActionPanel(modelProvider, modelProvider.getSelectionModel(), ClientGinjectorProvider.getEventBus()); } private void addActionButtons(final SystemTreeModelProvider modelProvider) { actionPanel.addActionButton(new WebAdminButtonDefinition<SystemTreeModel>(constants.treeExpandAll()) { @Override protected UICommand resolveCommand() { return new UICommand(constants.treeExpandAll(), new BaseCommandTarget() { @Override public void executeCommand(UICommand command) { TreeNode expandNode = findNode(getDataDisplayWidget().getRootTreeNode(), modelProvider.getSelectionModel().getSelectedObject()); if (expandNode != null) { expandTree(expandNode); } } }); } }); actionPanel.addActionButton(new WebAdminButtonDefinition<SystemTreeModel>(constants.treeCollapseAll()) { @Override protected UICommand resolveCommand() { return new UICommand(constants.treeCollapseAll(), new BaseCommandTarget() { @Override public void executeCommand(UICommand command) { TreeNode collapseNode = findNode(getDataDisplayWidget().getRootTreeNode(), modelProvider.getSelectionModel().getSelectedObject()); if (collapseNode != null) { collapseTree(collapseNode); } } }); } }); } private void addModelListeners(final SystemTreeModelProvider modelProvider) { final SystemTreeModel treeModel = modelProvider.getModel(); treeModel.getItemsChangedEvent().addListener((ev, sender, args) -> { if (modelProvider.getModel().getSelectedItem() == null) { expandTree(getDataDisplayWidget().getRootTreeNode(), ITEM_LEVEL); } else { expandPathUsingMap(getDataDisplayWidget().getRootTreeNode(), nodeStateMap); } }); treeModel.getSelectedItemChangedEvent().addListener((ev, sender, args) -> expandPath(modelProvider.getSelectionModel().getSelectedObject())); treeModel.getBeforeItemsChangedEvent().addListener((ev, sender, args) -> { //Empty the state map so we can capture the new state. nodeStateMap.clear(); getNodeOpenMap(getDataDisplayWidget().getRootTreeNode(), nodeStateMap ); }); } /** * This method captures the current expansion state of the tree before it gets refreshed. * @param treeNode The root node to capture. * @param expandedStateMap The map to store the state in. */ private void getNodeOpenMap(TreeNode treeNode, Map<Object, Map<String, Boolean>> expandedStateMap) { if (treeNode == null) { return; } for (int i = 0; i < treeNode.getChildCount(); i++) { if (treeNode.getChildValue(i) != null) { Object entity = ((SystemTreeItemModel) treeNode.getChildValue(i)).getEntity(); Map<String, Boolean> entityStringMap = expandedStateMap.get(entity); if (entityStringMap == null) { entityStringMap = new HashMap<>(); expandedStateMap.put(entity, entityStringMap); } entityStringMap.put(((SystemTreeItemModel) treeNode.getChildValue(i)).getTitle(), treeNode.isChildOpen(i)); } // This gets the child node, but doesn't change the open status (there's no other way to get the child). getNodeOpenMap(treeNode.setChildOpen(i, treeNode.isChildOpen(i)), expandedStateMap); } } /** * This method expands the system tree based on the state in the passed in map. The map is key * on both entity and title of the node as several nodes will have the same entity but will have different * titles. * @param rootNode The root node to expand. * @param expandedStateMap The map that contains the expansion state. */ private void expandPathUsingMap(TreeNode rootNode, Map<Object, Map<String, Boolean>> expandedStateMap) { if (rootNode == null) { return; } for (int i = 0; i < rootNode.getChildCount(); i++) { Boolean expandNode = false; Map<String, Boolean> entityStringMap = expandedStateMap.get(((SystemTreeItemModel)rootNode.getChildValue(i)).getEntity()); if (entityStringMap != null) { expandNode = entityStringMap.get(((SystemTreeItemModel)rootNode.getChildValue(i)).getTitle()); } if (expandNode != null && expandNode) { expandPathUsingMap(rootNode.setChildOpen(i, expandNode), expandedStateMap); } } } @Override protected CellTree createDataDisplayWidget(SystemTreeModelProvider modelProvider) { CellTree display = new ElementIdCellTree<SystemTreeModelProvider>(modelProvider, null, res) { @Override protected void onLoad() { expandTree(getDataDisplayWidget().getRootTreeNode(), ITEM_LEVEL); } }; display.setAnimationEnabled(true); modelProvider.setDataDisplay(display); return display; } private void expandPath(SystemTreeItemModel targetNodeModel) { if (targetNodeModel == null) { return; } // first construct a list of all ancestors of the target node List<SystemTreeItemModel> modelPath = new ArrayList<>(); SystemTreeItemModel model = targetNodeModel.getParent(); while (model != null) { modelPath.add(model); model = model.getParent(); } // then iterate over it in reverse to expand the ancestors in the widget TreeNode node = getDataDisplayWidget().getRootTreeNode(); ListIterator<SystemTreeItemModel> i = modelPath.listIterator(modelPath.size()); while (i.hasPrevious()) { model = i.previous(); // look for the child that fits the current node in the ancestry path of the target node for (int j=0; j < node.getChildCount(); ++j) { if (node.getChildValue(j).equals(model)) { node = node.setChildOpen(j, true); break; } } } } private void expandTree(TreeNode node) { expandTree(node, ALL_LEVELS); } private void expandTree(TreeNode node, int expandUpToLevel) { if (node == null) { return; } for (int i = 0; i < node.getChildCount(); i++) { boolean expandNode = 0 < expandUpToLevel ? true : false; expandTree(node.setChildOpen(i, expandNode), expandUpToLevel - 1); } } private void collapseTree(TreeNode node) { TreeNode parent = node.getParent(); if (parent == null) { node.setChildOpen(0, false); } else { parent.setChildOpen(node.getIndex(), false); } } private TreeNode findNode(TreeNode node, SystemTreeItemModel model) { TreeNode result = null; if (node == null) { return null; } int i = 0; while (result == null && i < node.getChildCount()) { if (model != null && model.equals(node.getChildValue(i))) { result = node.setChildOpen(i, true); break; } // Only check open nodes, otherwise they couldn't have been selected. if (node.isChildOpen(i)) { result = findNode(node.setChildOpen(i, true), model); } i++; } return result; } public interface SystemTreeResources extends CellTree.Resources { interface TableStyle extends CellTable.Style { } @Override @Source({ CellTree.Style.DEFAULT_CSS, "org/ovirt/engine/ui/webadmin/css/SystemTree.css" }) Style cellTreeStyle(); } }