1 /* 2 * Copyright (C) 2007 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.uimodel; 18 19 import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; 20 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; 21 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; 22 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; 23 import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; 24 import static com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors.ATTR_COLOR; 25 26 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 27 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 28 29 import org.eclipse.swt.widgets.Composite; 30 import org.eclipse.ui.forms.IManagedForm; 31 import org.w3c.dom.Node; 32 33 import java.util.Comparator; 34 35 /** 36 * Represents an XML attribute that can be modified by the XML editor's user interface. 37 * <p/> 38 * The characteristics of an {@link UiAttributeNode} are declared by a 39 * corresponding {@link AttributeDescriptor}. 40 * <p/> 41 * This is an abstract class. Derived classes must implement the creation of the UI 42 * and manage its synchronization with the XML. 43 */ 44 public abstract class UiAttributeNode implements Comparable<UiAttributeNode> { 45 46 private AttributeDescriptor mDescriptor; 47 private UiElementNode mUiParent; 48 private boolean mIsDirty; 49 private boolean mHasError; 50 51 /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} 52 * and the corresponding runtime {@link UiElementNode} parent. */ 53 public UiAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { 54 mDescriptor = attributeDescriptor; 55 mUiParent = uiParent; 56 } 57 58 /** Returns the {@link AttributeDescriptor} specific to this UI attribute node */ 59 public final AttributeDescriptor getDescriptor() { 60 return mDescriptor; 61 } 62 63 /** Returns the {@link UiElementNode} that owns this {@link UiAttributeNode} */ 64 public final UiElementNode getUiParent() { 65 return mUiParent; 66 } 67 68 /** Returns the current value of the node. */ 69 public abstract String getCurrentValue(); 70 71 /** 72 * @return True if the attribute has been changed since it was last loaded 73 * from the XML model. 74 */ 75 public final boolean isDirty() { 76 return mIsDirty; 77 } 78 79 /** 80 * Sets whether the attribute is dirty and also notifies the editor some part's dirty 81 * flag as changed. 82 * <p/> 83 * Subclasses should set the to true as a result of user interaction with the widgets in 84 * the section and then should set to false when the commit() method completed. 85 * 86 * @param isDirty the new value to set the dirty-flag to 87 */ 88 public void setDirty(boolean isDirty) { 89 boolean wasDirty = mIsDirty; 90 mIsDirty = isDirty; 91 // TODO: for unknown attributes, getParent() != null && getParent().getEditor() != null 92 if (wasDirty != isDirty) { 93 AndroidXmlEditor editor = getUiParent().getEditor(); 94 if (editor != null) { 95 editor.editorDirtyStateChanged(); 96 } 97 } 98 } 99 100 /** 101 * Sets the error flag value. 102 * @param errorFlag the error flag 103 */ 104 public final void setHasError(boolean errorFlag) { 105 mHasError = errorFlag; 106 } 107 108 /** 109 * Returns whether this node has errors. 110 */ 111 public final boolean hasError() { 112 return mHasError; 113 } 114 115 /** 116 * Called once by the parent user interface to creates the necessary 117 * user interface to edit this attribute. 118 * <p/> 119 * This method can be called more than once in the life cycle of an UI node, 120 * typically when the UI is part of a master-detail tree, as pages are swapped. 121 * 122 * @param parent The composite where to create the user interface. 123 * @param managedForm The managed form owning this part. 124 */ 125 public abstract void createUiControl(Composite parent, IManagedForm managedForm); 126 127 /** 128 * Used to get a list of all possible values for this UI attribute. 129 * <p/> 130 * This is used, among other things, by the XML Content Assists to complete values 131 * for an attribute. 132 * <p/> 133 * Implementations that do not have any known values should return null. 134 * 135 * @param prefix An optional prefix string, which is whatever the user has already started 136 * typing. Can be null or an empty string. The implementation can use this to filter choices 137 * and only return strings that match this prefix. A lazy or default implementation can 138 * simply ignore this and return everything. 139 * @return A list of possible completion values, and empty array or null. 140 */ 141 public abstract String[] getPossibleValues(String prefix); 142 143 /** 144 * Called when the XML is being loaded or has changed to 145 * update the value held by this user interface attribute node. 146 * <p/> 147 * The XML Node <em>may</em> be null, which denotes that the attribute is not 148 * specified in the XML model. In general, this means the "default" value of the 149 * attribute should be used. 150 * <p/> 151 * The caller doesn't really know if attributes have changed, 152 * so it will call this to refresh the attribute anyway. It's up to the 153 * UI implementation to minimize refreshes. 154 * 155 * @param node the node to read the value from 156 */ 157 public abstract void updateValue(Node node); 158 159 /** 160 * Called by the user interface when the editor is saved or its state changed 161 * and the modified attributes must be committed (i.e. written) to the XML model. 162 * <p/> 163 * Important behaviors: 164 * <ul> 165 * <li>The caller *must* have called IStructuredModel.aboutToChangeModel before. 166 * The implemented methods must assume it is safe to modify the XML model. 167 * <li>On success, the implementation *must* call setDirty(false). 168 * <li>On failure, the implementation can fail with an exception, which 169 * is trapped and logged by the caller, or do nothing, whichever is more 170 * appropriate. 171 * </ul> 172 */ 173 public abstract void commit(); 174 175 // ---- Implements Comparable ---- 176 public int compareTo(UiAttributeNode o) { 177 return compareAttributes(mDescriptor.getXmlLocalName(), o.mDescriptor.getXmlLocalName()); 178 } 179 180 /** 181 * Returns {@link Comparator} values for ordering attributes in the following 182 * order: 183 * <ul> 184 * <li> id 185 * <li> style 186 * <li> layout_width 187 * <li> layout_height 188 * <li> other layout params, sorted alphabetically 189 * <li> other attributes, sorted alphabetically 190 * </ul> 191 * 192 * @param name1 the first attribute name to compare 193 * @param name2 the second attribute name to compare 194 * @return a negative number if name1 should be ordered before name2 195 */ 196 public static int compareAttributes(String name1, String name2) { 197 int priority1 = getAttributePriority(name1); 198 int priority2 = getAttributePriority(name2); 199 if (priority1 != priority2) { 200 return priority1 - priority2; 201 } 202 203 // Sort remaining attributes alphabetically 204 return name1.compareTo(name2); 205 } 206 207 /** Returns a sorting priority for the given attribute name */ 208 private static int getAttributePriority(String name) { 209 if (ATTR_ID.equals(name)) { 210 return 10; 211 } 212 213 if (ATTR_STYLE.equals(name)) { 214 return 20; 215 } 216 217 if (name.startsWith(ATTR_LAYOUT_PREFIX)) { 218 // Width and height are special cased because we (a) want width and height 219 // before the other layout attributes, and (b) we want width to sort before height 220 // even though it comes after it alphabetically. 221 if (name.equals(ATTR_LAYOUT_WIDTH)) { 222 return 30; 223 } 224 if (name.equals(ATTR_LAYOUT_HEIGHT)) { 225 return 40; 226 } 227 228 return 50; 229 } 230 231 // "color" sorts to the end 232 if (ATTR_COLOR.equals(name)) { 233 return 100; 234 } 235 236 return 60; 237 } 238 } 239