1 /*
2  * Copyright (C) 2011 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.eclipse.adt.internal.editors.drawable;
17 
18 import static com.android.SdkConstants.ANDROID_NS_NAME;
19 import static com.android.SdkConstants.ANDROID_URI;
20 
21 import com.android.ide.common.api.IAttributeInfo.Format;
22 import com.android.ide.common.resources.platform.AttributeInfo;
23 import com.android.ide.common.resources.platform.DeclareStyleableInfo;
24 import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors;
25 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
26 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
27 import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor;
28 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
29 import com.android.resources.ResourceType;
30 
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 
36 /**
37  * Descriptors for /res/drawable files
38  */
39 public class DrawableDescriptors implements IDescriptorProvider {
40     private static final String SDK_URL_BASE =
41         "http://d.android.com/guide/topics/resources/"; //$NON-NLS-1$
42 
43     /** The root element descriptor */
44     private ElementDescriptor mDescriptor;
45     /** The root element descriptors */
46     private ElementDescriptor[] mRootDescriptors;
47     private Map<String, ElementDescriptor> nameToDescriptor;
48 
49     /** @return the root descriptor. */
50     @Override
getDescriptor()51     public ElementDescriptor getDescriptor() {
52         if (mDescriptor == null) {
53             mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$
54         }
55 
56         return mDescriptor;
57     }
58 
59     @Override
getRootElementDescriptors()60     public ElementDescriptor[] getRootElementDescriptors() {
61         return mRootDescriptors;
62     }
63 
64     /**
65      * Returns a descriptor for the given root tag name
66      *
67      * @param tag the tag name to look up a descriptor for
68      * @return a descriptor with the given tag name
69      */
getElementDescriptor(String tag)70     public ElementDescriptor getElementDescriptor(String tag) {
71         if (nameToDescriptor == null) {
72             nameToDescriptor = new HashMap<String, ElementDescriptor>();
73             for (ElementDescriptor descriptor : getRootElementDescriptors()) {
74                 nameToDescriptor.put(descriptor.getXmlName(), descriptor);
75             }
76         }
77 
78         ElementDescriptor descriptor = nameToDescriptor.get(tag);
79         if (descriptor == null) {
80             descriptor = getDescriptor();
81         }
82         return descriptor;
83     }
84 
updateDescriptors(Map<String, DeclareStyleableInfo> styleMap)85     public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) {
86         XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME,
87                 ANDROID_URI);
88         List<ElementDescriptor> descriptors = new ArrayList<ElementDescriptor>();
89 
90         AnimatorDescriptors.addElement(descriptors, styleMap,
91             "animation-list", "Animation List", "AnimationDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
92             "An animation defined in XML that shows a sequence of images in "
93                 + "order (like a film)",
94             SDK_URL_BASE + "animation-resource.html#Frame",
95             xmlns, null, true /*mandatory*/);
96 
97         /* For some reason, android.graphics.drawable.AnimatedRotateDrawable is marked with @hide
98          * so we should not register it and its attributes here. See issue #19248 for details.
99         AnimatorDescriptors.addElement(descriptors, styleMap,
100             "animated-rotate", "Animated Rotate", "AnimatedRotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
101             // Need docs
102             null, // tooltip
103             null, // sdk_url
104             xmlns, null, true);
105         */
106 
107         AnimatorDescriptors.addElement(descriptors, styleMap,
108             "bitmap", "BitMap", "BitmapDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
109             "An XML bitmap is a resource defined in XML that points to a bitmap file. "
110                     + "The effect is an alias for a raw bitmap file. The XML can "
111                     + "specify additional properties for the bitmap such as "
112                     + "dithering and tiling.",
113             SDK_URL_BASE + "drawable-resource.html#Bitmap", //$NON-NLS-1$
114             xmlns, null, true /* mandatory */);
115 
116         AnimatorDescriptors.addElement(descriptors, styleMap,
117             "clip", "Clip", "ClipDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
118             "An XML file that defines a drawable that clips another Drawable based on "
119                 + "this Drawable's current level value.",
120             SDK_URL_BASE + "drawable-resource.html#Clip", //$NON-NLS-1$
121             xmlns, null, true /*mandatory*/);
122 
123         AnimatorDescriptors.addElement(descriptors, styleMap,
124             "color", "Color", "ColorDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
125             "XML resource that carries a color value (a hexadecimal color)",
126             SDK_URL_BASE + "more-resources.html#Color",
127             xmlns, null, true /*mandatory*/);
128 
129         AnimatorDescriptors.addElement(descriptors, styleMap,
130             "inset", "Inset", "InsetDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
131             "An XML file that defines a drawable that insets another drawable by a "
132                 + "specified distance. This is useful when a View needs a background "
133                 + "drawble that is smaller than the View's actual bounds.",
134             SDK_URL_BASE + "drawable-resource.html#Inset", //$NON-NLS-1$
135             xmlns, null, true /*mandatory*/);
136 
137         // Layer list
138 
139         // An <item> in a <selector> or <
140         ElementDescriptor layerItem = AnimatorDescriptors.addElement(null, styleMap,
141                 "item", "Item", "LayerDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$
142                 "Defines a drawable to place in the layer drawable, in a position "
143                     + "defined by its attributes. Must be a child of a <selector> "
144                     + "element. Accepts child <bitmap> elements.",
145                 SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$
146                 null, /* extra attribute */
147                 null, /* This is wrong -- we can now embed any above drawable
148                             (but without xmlns as extra) */
149                 false /*mandatory*/);
150         ElementDescriptor[] layerChildren = layerItem != null
151             ? new ElementDescriptor[] { layerItem } : null;
152 
153         AnimatorDescriptors.addElement(descriptors, styleMap,
154             "layer-list", "Layer List", "LayerDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
155             "A Drawable that manages an array of other Drawables. These are drawn in "
156                     + "array order, so the element with the largest index is be drawn on top.",
157             SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$
158             xmlns,
159             layerChildren,
160             true /*mandatory*/);
161 
162         // Level list children
163         ElementDescriptor levelListItem = AnimatorDescriptors.addElement(null, styleMap,
164             "item", "Item", "LevelListDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$
165             "Defines a drawable to use at a certain level.",
166             SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$
167             null, /* extra attribute */
168             null, /* no further children */
169             // TODO: The inflation code seems to show that all drawables can be nested here!
170             false /*mandatory*/);
171         AnimatorDescriptors.addElement(descriptors, styleMap,
172             "level-list", "Level List", "LevelListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
173             "An XML file that defines a drawable that manages a number of alternate "
174                 + "Drawables, each assigned a maximum numerical value",
175             SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$
176             xmlns,
177             levelListItem != null ? new ElementDescriptor[] { levelListItem } : null,
178             true /*mandatory*/);
179 
180         // Not yet supported
181         //addElement(descriptors, styleMap, "mipmap", "Mipmap", "MipmapDrawable", null,
182         //        null /* tooltip */,
183         //        null /* sdk_url */,
184         //        xmlns, null, true /*mandatory*/);
185 
186         AnimatorDescriptors.addElement(descriptors, styleMap,
187             "nine-patch", "Nine Patch", "NinePatchDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
188             "A PNG file with stretchable regions to allow image resizing "
189                     + "based on content (.9.png).",
190             SDK_URL_BASE + "drawable-resource.html#NinePatch", //$NON-NLS-1$
191             xmlns, null, true /*mandatory*/);
192 
193         AnimatorDescriptors.addElement(descriptors, styleMap,
194             "rotate", "Rotate", "RotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
195             // Need docs
196             null /* tooltip */,
197             null /* sdk_url */,
198             xmlns, null, true /*mandatory*/);
199 
200         AnimatorDescriptors.addElement(descriptors, styleMap,
201             "scale", "Shape", "ScaleDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
202             "An XML file that defines a drawable that changes the size of another Drawable "
203                 + "based on its current level value.",
204             SDK_URL_BASE + "drawable-resource.html#Scale", //$NON-NLS-1$
205             xmlns, null, true /*mandatory*/);
206 
207         // Selector children
208         ElementDescriptor selectorItem = AnimatorDescriptors.addElement(null, styleMap,
209             "item", "Item", "DrawableStates", null, //$NON-NLS-1$ //$NON-NLS-3$
210             "Defines a drawable to use during certain states, as described by "
211                  + "its attributes. Must be a child of a <selector> element.",
212             SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$
213             new ReferenceAttributeDescriptor(
214                     ResourceType.DRAWABLE, "drawable", ANDROID_URI, //$NON-NLS-1$
215                     new AttributeInfo("drawable", Format.REFERENCE_SET))
216                     .setTooltip("Reference to a drawable resource."),
217             null, /* This is wrong -- we can now embed any above drawable
218                         (but without xmlns as extra) */
219             false /*mandatory*/);
220 
221         AnimatorDescriptors.addElement(descriptors, styleMap,
222             "selector", "Selector", "StateListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
223             "An XML file that references different bitmap graphics for different states "
224                 + "(for example, to use a different image when a button is pressed).",
225             SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$
226             xmlns,
227             selectorItem != null ? new ElementDescriptor[] { selectorItem } : null,
228             true /*mandatory*/);
229 
230         // Shape
231         // Shape children
232         List<ElementDescriptor> shapeChildren = new ArrayList<ElementDescriptor>();
233         // Selector children
234         AnimatorDescriptors.addElement(shapeChildren, styleMap,
235             "size", "Size", "GradientDrawableSize", null, //$NON-NLS-1$ //$NON-NLS-3$
236             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
237             null /* children */, false /* mandatory */);
238         AnimatorDescriptors.addElement(shapeChildren, styleMap,
239             "gradient", "Gradient", "GradientDrawableGradient", null, //$NON-NLS-1$ //$NON-NLS-3$
240             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
241             null /* children */, false /* mandatory */);
242         AnimatorDescriptors.addElement(shapeChildren, styleMap,
243             "solid", "Solid", "GradientDrawableSolid", null, //$NON-NLS-1$ //$NON-NLS-3$
244             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
245             null /* children */, false /* mandatory */);
246         AnimatorDescriptors.addElement(shapeChildren, styleMap,
247             "stroke", "Stroke", "GradientDrawableStroke", null, //$NON-NLS-1$ //$NON-NLS-3$
248             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
249             null /* children */, false /* mandatory */);
250         AnimatorDescriptors.addElement(shapeChildren, styleMap,
251             "corners", "Corners", "DrawableCorners", null, //$NON-NLS-1$ //$NON-NLS-3$
252             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
253             null /* children */, false /* mandatory */);
254         AnimatorDescriptors.addElement(shapeChildren, styleMap,
255             "padding", "Padding", "GradientDrawablePadding", null, //$NON-NLS-1$ //$NON-NLS-3$
256             null /* tooltip */, null /* sdk_url */, null /* extra attribute */,
257             null /* children */, false /* mandatory */);
258 
259         AnimatorDescriptors.addElement(descriptors, styleMap,
260             "shape", "Shape", //$NON-NLS-1$
261 
262             // The documentation says that a <shape> element creates a ShapeDrawable,
263             // but ShapeDrawable isn't finished and the code currently creates
264             // a GradientDrawable.
265             //"ShapeDrawable", //$NON-NLS-1$
266             "GradientDrawable", //$NON-NLS-1$
267 
268             null,
269             "An XML file that defines a geometric shape, including colors and gradients.",
270             SDK_URL_BASE + "drawable-resource.html#Shape", //$NON-NLS-1$
271             xmlns,
272 
273             // These are the GradientDrawable children, not the ShapeDrawable children
274             shapeChildren.toArray(new ElementDescriptor[shapeChildren.size()]),
275             true /*mandatory*/);
276 
277         AnimatorDescriptors.addElement(descriptors, styleMap,
278             "transition", "Transition", "TransitionDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$
279             "An XML file that defines a drawable that can cross-fade between two "
280                 + "drawable resources. Each drawable is represented by an <item> "
281                 + "element inside a single <transition> element. No more than two "
282                 + "items are supported. To transition forward, call startTransition(). "
283                 + "To transition backward, call reverseTransition().",
284             SDK_URL_BASE + "drawable-resource.html#Transition", //$NON-NLS-1$
285             xmlns,
286             layerChildren, // children: a TransitionDrawable is a LayerDrawable
287             true /*mandatory*/);
288 
289         mRootDescriptors = descriptors.toArray(new ElementDescriptor[descriptors.size()]);
290 
291         // A <selector><item> can contain any of the top level drawables
292         if (selectorItem != null) {
293             selectorItem.setChildren(mRootDescriptors);
294         }
295         // Docs says it can accept <bitmap> but code comment suggests any is possible;
296         // test and either use this or just { bitmap }
297         if (layerItem != null) {
298             layerItem.setChildren(mRootDescriptors);
299         }
300     }
301 }
302