1 /* 2 * Copyright (C) 2008 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 17 package com.android.ide.eclipse.adt.internal.editors.layout.descriptors; 18 19 import com.android.ide.eclipse.adt.AndroidConstants; 20 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 21 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; 22 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 23 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 24 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; 25 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; 26 import com.android.ide.eclipse.adt.internal.resources.DeclareStyleableInfo; 27 import com.android.ide.eclipse.adt.internal.resources.ViewClassInfo; 28 import com.android.ide.eclipse.adt.internal.resources.DeclareStyleableInfo.AttributeInfo; 29 import com.android.ide.eclipse.adt.internal.resources.ViewClassInfo.LayoutParamsInfo; 30 import com.android.sdklib.SdkConstants; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 36 37 /** 38 * Complete description of the layout structure. 39 */ 40 public final class LayoutDescriptors implements IDescriptorProvider { 41 42 // Public attributes names, attributes descriptors and elements descriptors 43 public static final String ID_ATTR = "id"; //$NON-NLS-1$ 44 45 /** The document descriptor. Contains all layouts and views linked together. */ 46 private DocumentDescriptor mRootDescriptor = 47 new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ 48 49 /** The list of all known ViewLayout descriptors. */ 50 private ArrayList<ElementDescriptor> mLayoutDescriptors = new ArrayList<ElementDescriptor>(); 51 52 /** Read-Only list of View Descriptors. */ 53 private List<ElementDescriptor> mROLayoutDescriptors; 54 55 /** The list of all known View (not ViewLayout) descriptors. */ 56 private ArrayList<ElementDescriptor> mViewDescriptors = new ArrayList<ElementDescriptor>(); 57 58 /** Read-Only list of View Descriptors. */ 59 private List<ElementDescriptor> mROViewDescriptors; 60 61 /** @return the document descriptor. Contains all layouts and views linked together. */ getDescriptor()62 public DocumentDescriptor getDescriptor() { 63 return mRootDescriptor; 64 } 65 66 /** @return The read-only list of all known ViewLayout descriptors. */ getLayoutDescriptors()67 public List<ElementDescriptor> getLayoutDescriptors() { 68 return mROLayoutDescriptors; 69 } 70 71 /** @return The read-only list of all known View (not ViewLayout) descriptors. */ getViewDescriptors()72 public List<ElementDescriptor> getViewDescriptors() { 73 return mROViewDescriptors; 74 } 75 getRootElementDescriptors()76 public ElementDescriptor[] getRootElementDescriptors() { 77 return mRootDescriptor.getChildren(); 78 } 79 80 /** 81 * Updates the document descriptor. 82 * <p/> 83 * It first computes the new children of the descriptor and then update them 84 * all at once. 85 * <p/> 86 * TODO: differentiate groups from views in the tree UI? => rely on icons 87 * <p/> 88 * 89 * @param views The list of views in the framework. 90 * @param layouts The list of layouts in the framework. 91 */ updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts)92 public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) { 93 ArrayList<ElementDescriptor> newViews = new ArrayList<ElementDescriptor>(); 94 if (views != null) { 95 for (ViewClassInfo info : views) { 96 ElementDescriptor desc = convertView(info); 97 newViews.add(desc); 98 } 99 } 100 101 // Create <include> as a synthetic regular view. 102 // Note: ViewStub is already described by attrs.xml 103 insertInclude(newViews); 104 105 ArrayList<ElementDescriptor> newLayouts = new ArrayList<ElementDescriptor>(); 106 if (layouts != null) { 107 for (ViewClassInfo info : layouts) { 108 ElementDescriptor desc = convertView(info); 109 newLayouts.add(desc); 110 } 111 } 112 113 ArrayList<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>(); 114 newDescriptors.addAll(newLayouts); 115 newDescriptors.addAll(newViews); 116 117 // Link all layouts to everything else here.. recursively 118 for (ElementDescriptor layoutDesc : newLayouts) { 119 layoutDesc.setChildren(newDescriptors); 120 } 121 122 // The <merge> tag can only be a root tag, so it is added at the end. 123 // It gets everything else as children but it is not made a child itself. 124 ElementDescriptor mergeTag = createMerge(newLayouts); 125 mergeTag.setChildren(newDescriptors); // mergeTag makes a copy of the list 126 newDescriptors.add(mergeTag); 127 newLayouts.add(mergeTag); 128 129 mViewDescriptors = newViews; 130 mLayoutDescriptors = newLayouts; 131 mRootDescriptor.setChildren(newDescriptors); 132 133 mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors); 134 mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors); 135 } 136 137 /** 138 * Creates an element descriptor from a given {@link ViewClassInfo}. 139 */ convertView(ViewClassInfo info)140 private ElementDescriptor convertView(ViewClassInfo info) { 141 String xml_name = info.getShortClassName(); 142 String tooltip = info.getJavaDoc(); 143 144 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(); 145 146 // All views and groups have an implicit "style" attribute which is a reference. 147 AttributeInfo styleInfo = new DeclareStyleableInfo.AttributeInfo( 148 "style", //$NON-NLS-1$ xmlLocalName 149 new DeclareStyleableInfo.AttributeInfo.Format[] { 150 DeclareStyleableInfo.AttributeInfo.Format.REFERENCE 151 }); 152 styleInfo.setJavaDoc("A reference to a custom style"); //tooltip 153 DescriptorsUtils.appendAttribute(attributes, 154 "style", //$NON-NLS-1$ 155 null, //nsUri 156 styleInfo, 157 false, //required 158 null); // overrides 159 160 // Process all View attributes 161 DescriptorsUtils.appendAttributes(attributes, 162 null, // elementName 163 SdkConstants.NS_RESOURCES, 164 info.getAttributes(), 165 null, // requiredAttributes 166 null /* overrides */); 167 168 for (ViewClassInfo link = info.getSuperClass(); 169 link != null; 170 link = link.getSuperClass()) { 171 AttributeInfo[] attrList = link.getAttributes(); 172 if (attrList.length > 0) { 173 attributes.add(new SeparatorAttributeDescriptor( 174 String.format("Attributes from %1$s", link.getShortClassName()))); 175 DescriptorsUtils.appendAttributes(attributes, 176 null, // elementName 177 SdkConstants.NS_RESOURCES, 178 attrList, 179 null, // requiredAttributes 180 null /* overrides */); 181 } 182 } 183 184 // Process all LayoutParams attributes 185 ArrayList<AttributeDescriptor> layoutAttributes = new ArrayList<AttributeDescriptor>(); 186 LayoutParamsInfo layoutParams = info.getLayoutData(); 187 188 for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) { 189 boolean need_separator = true; 190 for (AttributeInfo attr_info : layoutParams.getAttributes()) { 191 if (DescriptorsUtils.containsAttribute(layoutAttributes, 192 SdkConstants.NS_RESOURCES, attr_info)) { 193 continue; 194 } 195 if (need_separator) { 196 String title; 197 if (layoutParams.getShortClassName().equals( 198 AndroidConstants.CLASS_NAME_LAYOUTPARAMS)) { 199 title = String.format("Layout Attributes from %1$s", 200 layoutParams.getViewLayoutClass().getShortClassName()); 201 } else { 202 title = String.format("Layout Attributes from %1$s (%2$s)", 203 layoutParams.getViewLayoutClass().getShortClassName(), 204 layoutParams.getShortClassName()); 205 } 206 layoutAttributes.add(new SeparatorAttributeDescriptor(title)); 207 need_separator = false; 208 } 209 DescriptorsUtils.appendAttribute(layoutAttributes, 210 null, // elementName 211 SdkConstants.NS_RESOURCES, 212 attr_info, 213 false, // required 214 null /* overrides */); 215 } 216 } 217 218 return new ViewElementDescriptor(xml_name, 219 xml_name, // ui_name 220 info.getCanonicalClassName(), 221 tooltip, 222 null, // sdk_url 223 attributes.toArray(new AttributeDescriptor[attributes.size()]), 224 layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), 225 null, // children 226 false /* mandatory */); 227 } 228 229 /** 230 * Creates a new <include> descriptor and adds it to the list of view descriptors. 231 * 232 * @param knownViews A list of view descriptors being populated. Also used to find the 233 * View descriptor and extract its layout attributes. 234 */ insertInclude(ArrayList<ElementDescriptor> knownViews)235 private void insertInclude(ArrayList<ElementDescriptor> knownViews) { 236 String xml_name = "include"; //$NON-NLS-1$ 237 238 // Create the include custom attributes 239 ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>(); 240 241 // Note that the "layout" attribute does NOT have the Android namespace 242 DescriptorsUtils.appendAttribute(attributes, 243 null, //elementXmlName 244 null, //nsUri 245 new AttributeInfo( 246 "layout", //$NON-NLS-1$ 247 new AttributeInfo.Format[] { AttributeInfo.Format.REFERENCE } 248 ), 249 true, //required 250 null); //overrides 251 252 DescriptorsUtils.appendAttribute(attributes, 253 null, //elementXmlName 254 SdkConstants.NS_RESOURCES, //nsUri 255 new AttributeInfo( 256 "id", //$NON-NLS-1$ 257 new AttributeInfo.Format[] { AttributeInfo.Format.REFERENCE } 258 ), 259 true, //required 260 null); //overrides 261 262 // Find View and inherit all its layout attributes 263 AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes( 264 AndroidConstants.CLASS_VIEW, knownViews); 265 266 // Create the include descriptor 267 ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name 268 xml_name, // ui_name 269 null, // canonical class name, we don't have one 270 "Lets you statically include XML layouts inside other XML layouts.", // tooltip 271 null, // sdk_url 272 attributes.toArray(new AttributeDescriptor[attributes.size()]), 273 viewLayoutAttribs, // layout attributes 274 null, // children 275 false /* mandatory */); 276 277 knownViews.add(desc); 278 } 279 280 /** 281 * Creates and return a new <merge> descriptor. 282 * @param knownLayouts A list of all known layout view descriptors, used to find the 283 * FrameLayout descriptor and extract its layout attributes. 284 */ createMerge(ArrayList<ElementDescriptor> knownLayouts)285 private ElementDescriptor createMerge(ArrayList<ElementDescriptor> knownLayouts) { 286 String xml_name = "merge"; //$NON-NLS-1$ 287 288 // Find View and inherit all its layout attributes 289 AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes( 290 AndroidConstants.CLASS_FRAMELAYOUT, knownLayouts); 291 292 // Create the include descriptor 293 ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name 294 xml_name, // ui_name 295 null, // canonical class name, we don't have one 296 "A root tag useful for XML layouts inflated using a ViewStub.", // tooltip 297 null, // sdk_url 298 null, // attributes 299 viewLayoutAttribs, // layout attributes 300 null, // children 301 false /* mandatory */); 302 303 return desc; 304 } 305 306 /** 307 * Finds the descriptor and retrieves all its layout attributes. 308 */ findViewLayoutAttributes( String viewFqcn, ArrayList<ElementDescriptor> knownViews)309 private AttributeDescriptor[] findViewLayoutAttributes( 310 String viewFqcn, 311 ArrayList<ElementDescriptor> knownViews) { 312 313 for (ElementDescriptor desc : knownViews) { 314 if (desc instanceof ViewElementDescriptor) { 315 ViewElementDescriptor viewDesc = (ViewElementDescriptor) desc; 316 if (viewFqcn.equals(viewDesc.getCanonicalClassName())) { 317 return viewDesc.getLayoutAttributes(); 318 } 319 } 320 } 321 322 return null; 323 } 324 } 325