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