// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.collide.client.code.debugging; import com.google.collide.client.ui.dropdown.DropdownController; import com.google.collide.client.ui.dropdown.DropdownWidgets; import com.google.collide.client.ui.dropdown.DropdownController.DropdownPositionerBuilder; import com.google.collide.client.ui.list.SimpleList.ListItemRenderer; import com.google.collide.client.ui.menu.PositionController.HorizontalAlign; import com.google.collide.client.ui.menu.PositionController.Positioner; import com.google.collide.client.ui.tree.TreeNodeElement; import com.google.collide.client.ui.tree.TreeNodeLabelRenamer; import com.google.collide.client.ui.tree.TreeNodeMutator; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.util.JsonCollections; import com.google.collide.shared.util.StringUtils; import elemental.html.Element; /** * Handles {@link RemoteObjectTree} tree context menu actions. * */ class RemoteObjectTreeContextMenuController { /** * Callbacks on the context menu actions. */ interface Listener { void onAddNewChild(TreeNodeElement<RemoteObjectNode> node); void onNodeEdited(TreeNodeElement<RemoteObjectNode> node, String newLabel); void onNodeDeleted(TreeNodeElement<RemoteObjectNode> node); void onNodeRenamed(TreeNodeElement<RemoteObjectNode> node, String oldLabel); } /** * A menu item in the context menu. */ private interface TreeNodeMenuItem { public void onClicked(TreeNodeElement<RemoteObjectNode> node); @Override public String toString(); } /** * Renderer for an item in the context menu. */ class TreeItemRenderer extends ListItemRenderer<TreeNodeMenuItem> { @Override public void render(Element listItemBase, TreeNodeMenuItem item) { listItemBase.setTextContent(item.toString()); } } static RemoteObjectTreeContextMenuController create(DropdownWidgets.Resources resources, DebuggerState debuggerState, RemoteObjectNodeRenderer nodeRenderer, TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator) { RemoteObjectTreeContextMenuController controller = new RemoteObjectTreeContextMenuController(debuggerState, nodeRenderer, nodeLabelMutator); controller.installContextMenuController(resources); return controller; } private final DebuggerState debuggerState; private DropdownController<TreeNodeMenuItem> contextDropdownController; private final RemoteObjectNodeRenderer nodeRenderer; private final TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator; private Listener listener; private TreeNodeElement<RemoteObjectNode> selectedNode; private TreeItemRenderer renderer; private TreeNodeMenuItem menuRename; private TreeNodeMenuItem menuDelete; private TreeNodeMenuItem menuAdd; private TreeNodeMenuItem menuEdit; private RemoteObjectTreeContextMenuController(DebuggerState debuggerState, RemoteObjectNodeRenderer nodeRenderer, TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator) { this.debuggerState = debuggerState; this.nodeRenderer = nodeRenderer; this.nodeLabelMutator = nodeLabelMutator; this.renderer = new TreeItemRenderer(); createMenuItems(); } void setListener(Listener listener) { this.listener = listener; } private void installContextMenuController(DropdownWidgets.Resources res) { DropdownController.Listener<TreeNodeMenuItem> listener = new DropdownController.BaseListener<TreeNodeMenuItem>() { @Override public void onItemClicked(TreeNodeMenuItem item) { item.onClicked(selectedNode); } }; Positioner mousePositioner = new DropdownPositionerBuilder().setHorizontalAlign( HorizontalAlign.RIGHT).buildMousePositioner(); contextDropdownController = new DropdownController.Builder<TreeNodeMenuItem>( mousePositioner, null, res, listener, renderer).setShouldAutoFocusOnOpen(true).build(); } void show(int mouseX, int mouseY, TreeNodeElement<RemoteObjectNode> nodeElement) { selectedNode = nodeElement; if (nodeElement == null) { return; } RemoteObjectNode node = nodeElement.getData(); JsonArray<TreeNodeMenuItem> menuItems = JsonCollections.createArray(); if (canAdd(node)) { menuItems.add(menuAdd); } if (canEdit(node)) { menuItems.add(menuEdit); } if (canRenameAndDelete(node)) { menuItems.add(menuRename); menuItems.add(menuDelete); } if (!menuItems.isEmpty()) { contextDropdownController.setItems(menuItems); contextDropdownController.showAtPosition(mouseX, mouseY); } else { contextDropdownController.hide(); } } void hide() { contextDropdownController.hide(); } Element getContextMenuElement() { return contextDropdownController.getElement(); } private boolean canAdd(RemoteObjectNode node) { return node.canAddRemoteObjectProperty(); } private boolean canEdit(RemoteObjectNode node) { return node.isWritable() && debuggerState.isActive() && (node.isRootChild() || isRealRemoteObject(node.getParent())); } private boolean canRenameAndDelete(RemoteObjectNode node) { return node.isDeletable() && (node.isRootChild() || isRealRemoteObject(node.getParent())); } private static boolean isRealRemoteObject(RemoteObjectNode node) { return node != null && node.getRemoteObject() != null && !node.isTransient(); } private void enterAddProperty(TreeNodeElement<RemoteObjectNode> node) { if (listener != null) { listener.onAddNewChild(node); } } boolean enterEditPropertyValue(TreeNodeElement<RemoteObjectNode> nodeToEdit) { if (!canEdit(nodeToEdit.getData())) { return false; } nodeLabelMutator.cancel(); nodeLabelMutator.getTreeNodeMutator().enterMutation( nodeToEdit, new TreeNodeMutator.MutationAction<RemoteObjectNode>() { @Override public Element getElementForMutation(TreeNodeElement<RemoteObjectNode> node) { return nodeRenderer.getPropertyValueElement(node.getNodeLabel()); } @Override public void onBeforeMutation(TreeNodeElement<RemoteObjectNode> node) { nodeRenderer.enterPropertyValueMutation(node.getNodeLabel()); } @Override public void onMutationCommit( TreeNodeElement<RemoteObjectNode> node, String oldLabel, String newLabel) { String trimmedLabel = StringUtils.trimNullToEmpty(newLabel); nodeRenderer.exitPropertyValueMutation(node.getNodeLabel(), trimmedLabel); if (listener != null && !StringUtils.equalStringsOrEmpty(oldLabel, trimmedLabel)) { listener.onNodeEdited(node, trimmedLabel); } } @Override public boolean passValidation(TreeNodeElement<RemoteObjectNode> node, String newLabel) { return !StringUtils.isNullOrWhitespace(newLabel); } }); return true; } boolean enterRenameProperty(TreeNodeElement<RemoteObjectNode> nodeToRename) { if (!canRenameAndDelete(nodeToRename.getData())) { return false; } nodeLabelMutator.cancel(); nodeLabelMutator.enterMutation( nodeToRename, new TreeNodeLabelRenamer.LabelRenamerCallback<RemoteObjectNode>() { @Override public void onCommit(String oldLabel, TreeNodeElement<RemoteObjectNode> node) { if (listener != null && !oldLabel.equals(node.getData().getName())) { listener.onNodeRenamed(node, oldLabel); } } @Override public boolean passValidation(TreeNodeElement<RemoteObjectNode> node, String newLabel) { return !StringUtils.isNullOrWhitespace(newLabel); } }); return true; } private void enterDeleteProperty(TreeNodeElement<RemoteObjectNode> node) { if (listener != null) { listener.onNodeDeleted(node); } } private void createMenuItems() { menuRename = new TreeNodeMenuItem() { @Override public void onClicked(TreeNodeElement<RemoteObjectNode> node) { enterRenameProperty(node); } @Override public String toString() { return "Rename"; } }; menuDelete = new TreeNodeMenuItem() { @Override public void onClicked(TreeNodeElement<RemoteObjectNode> node) { enterDeleteProperty(node); } @Override public String toString() { return "Delete"; } }; menuAdd = new TreeNodeMenuItem() { @Override public void onClicked(TreeNodeElement<RemoteObjectNode> node) { enterAddProperty(node); } @Override public String toString() { return "Add"; } }; menuEdit = new TreeNodeMenuItem() { @Override public void onClicked(TreeNodeElement<RemoteObjectNode> node) { enterEditPropertyValue(node); } @Override public String toString() { return "Edit"; } }; } }