1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.ide.common.layout; 17 18 import static com.android.ide.common.layout.LayoutConstants.FQCN_TABLE_ROW; 19 20 import com.android.ide.common.api.DropFeedback; 21 import com.android.ide.common.api.IClientRulesEngine; 22 import com.android.ide.common.api.IMenuCallback; 23 import com.android.ide.common.api.INode; 24 import com.android.ide.common.api.INodeHandler; 25 import com.android.ide.common.api.IViewRule; 26 import com.android.ide.common.api.InsertType; 27 import com.android.ide.common.api.RuleAction; 28 import com.android.ide.common.api.SegmentType; 29 30 import java.net.URL; 31 import java.util.Collections; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Set; 35 36 /** 37 * An {@link IViewRule} for android.widget.TableLayout. 38 */ 39 public class TableLayoutRule extends LinearLayoutRule { 40 // A table is a linear layout, but with a few differences: 41 // the default is vertical, not horizontal 42 // The fill of all children should be wrap_content 43 44 private static final String ACTION_ADD_ROW = "_addrow"; //$NON-NLS-1$ 45 private static final String ACTION_REMOVE_ROW = "_removerow"; //$NON-NLS-1$ 46 private static final URL ICON_ADD_ROW = 47 TableLayoutRule.class.getResource("addrow.png"); //$NON-NLS-1$ 48 private static final URL ICON_REMOVE_ROW = 49 TableLayoutRule.class.getResource("removerow.png"); //$NON-NLS-1$ 50 51 @Override isVertical(INode node)52 protected boolean isVertical(INode node) { 53 // Tables are always vertical 54 return true; 55 } 56 57 @Override supportsOrientation()58 protected boolean supportsOrientation() { 59 return false; 60 } 61 62 @Override onChildInserted(INode child, INode parent, InsertType insertType)63 public void onChildInserted(INode child, INode parent, InsertType insertType) { 64 // Overridden to inhibit the setting of layout_width/layout_height since 65 // it should always be match_parent 66 } 67 68 /** 69 * Add an explicit "Add Row" action to the context menu 70 */ 71 @Override addContextMenuActions(List<RuleAction> actions, final INode selectedNode)72 public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { 73 super.addContextMenuActions(actions, selectedNode); 74 75 IMenuCallback addTab = new IMenuCallback() { 76 @Override 77 public void action(RuleAction action, List<? extends INode> selectedNodes, 78 final String valueId, Boolean newValue) { 79 final INode node = selectedNode; 80 INode newRow = node.appendChild(FQCN_TABLE_ROW); 81 mRulesEngine.select(Collections.singletonList(newRow)); 82 } 83 }; 84 actions.add(RuleAction.createAction("_addrow", "Add Row", addTab, null, 5, false)); //$NON-NLS-1$ 85 } 86 87 @Override addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children)88 public void addLayoutActions(List<RuleAction> actions, final INode parentNode, 89 final List<? extends INode> children) { 90 super.addLayoutActions(actions, parentNode, children); 91 addTableLayoutActions(mRulesEngine, actions, parentNode, children); 92 } 93 94 /** 95 * Adds layout actions to add and remove toolbar items 96 */ addTableLayoutActions(final IClientRulesEngine rulesEngine, List<RuleAction> actions, final INode parentNode, final List<? extends INode> children)97 static void addTableLayoutActions(final IClientRulesEngine rulesEngine, 98 List<RuleAction> actions, final INode parentNode, 99 final List<? extends INode> children) { 100 IMenuCallback actionCallback = new IMenuCallback() { 101 @Override 102 public void action(final RuleAction action, List<? extends INode> selectedNodes, 103 final String valueId, final Boolean newValue) { 104 parentNode.editXml("Add/Remove Table Row", new INodeHandler() { 105 @Override 106 public void handle(INode n) { 107 if (action.getId().equals(ACTION_ADD_ROW)) { 108 // Determine the index of the selection, if any; if there is 109 // a selection, insert the row before the current row, otherwise 110 // append it to the table. 111 int index = -1; 112 INode[] rows = parentNode.getChildren(); 113 if (children != null) { 114 findTableIndex: 115 for (INode child : children) { 116 // Find direct child of table layout 117 while (child != null && child.getParent() != parentNode) { 118 child = child.getParent(); 119 } 120 if (child != null) { 121 // Compute index of direct child of table layout 122 for (int i = 0; i < rows.length; i++) { 123 if (rows[i] == child) { 124 index = i; 125 break findTableIndex; 126 } 127 } 128 } 129 } 130 } 131 INode newRow; 132 if (index == -1) { 133 newRow = parentNode.appendChild(FQCN_TABLE_ROW); 134 } else { 135 newRow = parentNode.insertChildAt(FQCN_TABLE_ROW, index); 136 } 137 rulesEngine.select(Collections.singletonList(newRow)); 138 } else if (action.getId().equals(ACTION_REMOVE_ROW)) { 139 // Find the direct children of the TableLayout to delete; 140 // this is necessary since TableRow might also use 141 // this implementation, so the parentNode is the true 142 // TableLayout but the children might be grand children. 143 Set<INode> targets = new HashSet<INode>(); 144 for (INode child : children) { 145 while (child != null && child.getParent() != parentNode) { 146 child = child.getParent(); 147 } 148 if (child != null) { 149 targets.add(child); 150 } 151 } 152 for (INode target : targets) { 153 parentNode.removeChild(target); 154 } 155 } 156 } 157 }); 158 } 159 }; 160 161 // Add Row 162 actions.add(RuleAction.createSeparator(150)); 163 actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Table Row", actionCallback, 164 ICON_ADD_ROW, 160, false)); 165 166 // Remove Row (if something is selected) 167 if (children != null && children.size() > 0) { 168 actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Table Row", 169 actionCallback, ICON_REMOVE_ROW, 170, false)); 170 } 171 } 172 173 @Override onCreate(INode node, INode parent, InsertType insertType)174 public void onCreate(INode node, INode parent, InsertType insertType) { 175 super.onCreate(node, parent, insertType); 176 177 if (insertType.isCreate()) { 178 // Start the table with 4 rows 179 for (int i = 0; i < 4; i++) { 180 node.appendChild(FQCN_TABLE_ROW); 181 } 182 } 183 } 184 185 @Override onResizeBegin(INode child, INode parent, SegmentType horizontalEdge, SegmentType verticalEdge, Object childView, Object parentView)186 public DropFeedback onResizeBegin(INode child, INode parent, SegmentType horizontalEdge, 187 SegmentType verticalEdge, Object childView, Object parentView) { 188 // Children of a table layout cannot set their widths (it is controlled by column 189 // settings on the table). They can set their heights (though for TableRow, the 190 // height is always wrap_content). 191 if (horizontalEdge == null) { // Widths are edited by vertical edges. 192 // The user is not editing a vertical height so don't allow resizing at all 193 return null; 194 } 195 if (child.getFqcn().equals(FQCN_TABLE_ROW)) { 196 // TableRows are always WRAP_CONTENT 197 return null; 198 } 199 200 // Allow resizing heights only 201 return super.onResizeBegin(child, parent, horizontalEdge, null /*verticalEdge*/, 202 childView, parentView); 203 } 204 } 205