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