* right, top and bottom margins respectively
*/
String[] displayMarginInput(String all, String left, String right, String top, String bottom);
+
+ /**
+ * Displays an input dialog tailored for inputing the source of an {@code <include>}
+ * layout tag. This is similar to {@link #displayResourceInput} for resource type
+ * "layout", but should also attempt to filter out layout resources that cannot be
+ * included from the current context (because it would result in a cyclic dependency).
+ *
+ * @return the layout resource to include
+ */
+ String displayIncludeSourceInput();
}
INode insertChildAt(String viewFqcn, int index);
/**
+ * Removes the given XML element child from this node's list of children.
+ * <p/>
+ * This call must be done in the context of editXml().
+ *
+ * @param node The child to be deleted.
+ */
+ void removeChild(INode node);
+
+ /**
* Sets an attribute for the underlying XML element.
* Attributes are not written immediately -- instead the XML editor batches edits and
* then commits them all together at once later.
/** The view is newly created (by for example a palette drag) */
CREATE,
+ /**
+ * Same as {@link #CREATE} but when the views are constructed for previewing, for
+ * example as part of a palette drag.
+ */
+ CREATE_PREVIEW,
+
/** The view is being inserted here because it was moved from somewhere else */
MOVE,
* (including drags, but not from the palette)
*/
PASTE;
+
+ /**
+ * Returns true if this insert type is for a newly created view (for example a by
+ * palette drag). Note that this includes both normal create events as well as well as
+ * views created as part of previewing operations.
+ *
+ * @return true if this {@link InsertType} is for a newly created view
+ */
+ public boolean isCreate() {
+ return this == CREATE || this == CREATE_PREVIEW;
+ }
}
}
return value;
}
-
- private static class PropertySettingNodeHandler implements INodeHandler {
- private final String mNamespaceUri;
- private final String mAttribute;
- private final String mValue;
-
- public PropertySettingNodeHandler(String namespaceUri, String attribute, String value) {
- super();
- mNamespaceUri = namespaceUri;
- mAttribute = attribute;
- mValue = value;
- }
-
- public void handle(INode node) {
- node.setAttribute(mNamespaceUri, mAttribute, mValue);
- }
- }
}
super.onCreate(node, parent, insertType);
// A DialerFilter requires a couple of nested EditTexts with fixed ids:
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
String fillParent = getFillParentValueName();
INode hint = node.appendChild(FQCN_EDIT_TEXT);
hint.setAttribute(ANDROID_URI, ATTR_TEXT, "Hint");
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
// Insert a horizontal linear layout which is commonly used with horizontal scrollbars
// as described by the documentation for HorizontalScrollbars.
INode linearLayout = node.appendChild(FQCN_LINEAR_LAYOUT);
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc());
}
}
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc());
}
}
--- /dev/null
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.ATTR_LAYOUT;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.IViewRule;
+import com.android.ide.common.api.InsertType;
+
+/**
+ * An {@link IViewRule} for the special XML {@code <include>} tag.
+ */
+public class IncludeRule extends BaseViewRule {
+ @Override
+ public void onCreate(INode node, INode parent, InsertType insertType) {
+ // When dropping an include tag, ask the user which layout to include.
+ if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW
+ String include = mRulesEngine.displayIncludeSourceInput();
+ if (include != null) {
+ node.editXml("Include Layout",
+ // Note -- the layout attribute is NOT in the Android namespace!
+ new PropertySettingNodeHandler(null, ATTR_LAYOUT, include));
+ } else {
+ // Remove the view; the insertion was canceled
+ parent.removeChild(node);
+ }
+ }
+ }
+}
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
node.setAttribute(ANDROID_URI, "android:apiKey", //$NON-NLS-1$
"Your API key: see " + //$NON-NLS-1$
"http://code.google.com/android/add-ons/google-apis/mapkey.html"); //$NON-NLS-1$
--- /dev/null
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.INodeHandler;
+
+/**
+ * A convenience implementation of {@link INodeHandler} for setting a given attribute to a
+ * given value on a particular node.
+ */
+class PropertySettingNodeHandler implements INodeHandler {
+ private final String mNamespaceUri;
+ private final String mAttribute;
+ private final String mValue;
+
+ PropertySettingNodeHandler(String namespaceUri, String attribute, String value) {
+ super();
+ mNamespaceUri = namespaceUri;
+ mAttribute = attribute;
+ mValue = value;
+ }
+
+ public void handle(INode node) {
+ node.setAttribute(mNamespaceUri, mAttribute, mValue);
+ }
+}
\ No newline at end of file
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
for (int i = 0; i < 3; i++) {
INode handle = node.appendChild(LayoutConstants.FQCN_RADIO_BUTTON);
handle.setAttribute(ANDROID_URI, ATTR_ID, String.format("@+id/radio%d", i));
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
// Insert a default linear layout (which will in turn be registered as
// a child of this node and the create child method above will set its
// fill parent attributes, its id, etc.
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
String matchParent = getFillParentValueName();
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent);
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent);
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
String fillParent = getFillParentValueName();
// Configure default Table setup as described in the Table tutorial
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
String matchParent = getFillParentValueName();
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent);
node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent);
public void onCreate(INode node, INode parent, InsertType insertType) {
super.onCreate(node, parent, insertType);
- if (insertType == InsertType.CREATE) {
+ if (insertType.isCreate()) {
node.setAttribute(ANDROID_URI, ATTR_SRC, "@android:drawable/btn_plus"); //$NON-NLS-1$
}
}
import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
import static com.android.resources.ResourceType.LAYOUT;
-
import static org.eclipse.core.resources.IResourceDelta.ADDED;
import static org.eclipse.core.resources.IResourceDelta.CHANGED;
import static org.eclipse.core.resources.IResourceDelta.CONTENT;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
}
}
- /** For test suite only -- do not call */
@VisibleForTesting
/* package */ List<String> getIncludedBy(String included) {
ensureInitialized();
public static Reference create(IFile file) {
return new Reference(file.getProject(), getMapKey(file));
}
+
+ /**
+ * Returns the resource name of this layout, such as {@code @layout/foo}.
+ *
+ * @return the resource name
+ */
+ public String getResourceName() {
+ return '@' + FD_RES_LAYOUT + '/' + getName();
+ }
+ }
+
+ /**
+ * Returns a collection of layouts (expressed as resource names, such as
+ * {@code @layout/foo} which would be invalid includes in the given layout
+ * (because it would introduce a cycle)
+ *
+ * @param layout the layout file to check for cyclic dependencies from
+ * @return a collection of layout resources which cannot be included from
+ * the given layout, never null
+ */
+ public Collection<String> getInvalidIncludes(IFile layout) {
+ IProject project = layout.getProject();
+ Reference self = Reference.create(layout);
+
+ // Add anyone who transitively can reach this file via includes.
+ LinkedList<Reference> queue = new LinkedList<Reference>();
+ List<Reference> invalid = new ArrayList<Reference>();
+ queue.add(self);
+ invalid.add(self);
+ Set<String> seen = new HashSet<String>();
+ seen.add(self.getId());
+ while (!queue.isEmpty()) {
+ Reference reference = queue.removeFirst();
+ String refId = reference.getId();
+
+ // Look up both configuration specific includes as well as includes in the
+ // base versions
+ List<String> included = getIncludedBy(refId);
+ if (refId.indexOf('/') != -1) {
+ List<String> baseIncluded = getIncludedBy(reference.getName());
+ if (included == null) {
+ included = baseIncluded;
+ } else if (baseIncluded != null) {
+ included = new ArrayList<String>(included);
+ included.addAll(baseIncluded);
+ }
+ }
+
+ if (included != null && included.size() > 0) {
+ for (String id : included) {
+ if (!seen.contains(id)) {
+ seen.add(id);
+ Reference ref = new Reference(project, id);
+ invalid.add(ref);
+ queue.addLast(ref);
+ }
+ }
+ }
+ }
+
+ List<String> result = new ArrayList<String>();
+ for (Reference reference : invalid) {
+ result.add(reference.getResourceName());
+ }
+
+ return result;
}
}
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite;
import com.android.ide.eclipse.adt.internal.editors.ui.IDecorContent;
/** Performs the actual rendering of the descriptor into an image */
private Image renderPreview() {
+ ViewMetadataRepository repository = ViewMetadataRepository.get();
+ RenderMode renderMode = repository.getRenderMode(mDesc.getFullClassName());
+ if (renderMode == RenderMode.SKIP) {
+ return null;
+ }
+
// Create blank XML document
Document document = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
UiViewElementNode childUiNode = (UiViewElementNode) child;
NodeProxy childNode = nodeFactory.create(childUiNode);
canvas.getRulesEngine().callCreateHooks(layoutEditor,
- null, childNode, InsertType.CREATE);
+ null, childNode, InsertType.CREATE_PREVIEW);
}
Integer overrideBgColor = null;
return insertOrAppend(viewFqcn, index);
}
+ public void removeChild(INode node) {
+ checkEditOK();
+
+ ((NodeProxy) node).mNode.deleteXmlNode();
+ }
+
private INode insertOrAppend(String viewFqcn, int index) {
checkEditOK();
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleElement;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
}
public String displayResourceInput(String resourceTypeName, String currentValue) {
+ return displayResourceInput(resourceTypeName, currentValue, null);
+ }
+
+ private String displayResourceInput(String resourceTypeName, String currentValue,
+ IInputValidator validator) {
AndroidXmlEditor editor = mEditor.getLayoutEditor();
IProject project = editor.getProject();
ResourceType type = ResourceType.getEnum(resourceTypeName);
ResourceChooser dlg = new ResourceChooser(project, type, projectRepository,
systemRepository, shell);
+ if (validator != null) {
+ // Ensure wide enough to accommodate validator error message
+ dlg.setSize(70, 10);
+ dlg.setInputValidator(validator);
+ }
+
dlg.setCurrentResource(currentValue);
if (dlg.open() == Window.OK) {
return null;
}
+
+ public String displayIncludeSourceInput() {
+ AndroidXmlEditor editor = mEditor.getLayoutEditor();
+ IProject project = editor.getProject();
+ if (project != null) {
+ IncludeFinder includeFinder = IncludeFinder.get(project);
+ final Collection<String> invalid =
+ includeFinder.getInvalidIncludes(editor.getInputFile());
+ IInputValidator validator = new IInputValidator() {
+ public String isValid(String newText) {
+ if (invalid.contains(newText)) {
+ return String.format(
+ "Cyclic include, not valid",
+ newText);
+ }
+ return null;
+ }
+ };
+
+ return displayResourceInput(ResourceType.LAYOUT.getName(), null, validator);
+ }
+
+ return null;
+ }
}
}
fill="opposite"
render="skip" />
<view
+ class="include"
+ name="Include Other Layout"
+ render="skip" />
+ <view
class="android.widget.TableLayout"
fill="opposite"
render="skip" />
<view
class="android.widget.ZoomControls" />
<view
- class="include"
- skip="true"
- render="skip" />
- <view
class="merge"
skip="true"
render="skip" />
import static com.android.ide.common.layout.LayoutConstants.FQCN_RELATIVE_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.RELATIVE_LAYOUT;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
class ChangeLayoutWizard extends VisualRefactoringWizard {
// We don't exclude RelativeLayout even if the current layout is RelativeLayout,
// in case you are trying to flatten the hierarchy for a hierarchy that has a
// RelativeLayout at the root.
+ Set<String> exclude = new HashSet<String>();
+ exclude.add(VIEW_INCLUDE);
boolean oldIsRelativeLayout = mOldType.equals(FQCN_RELATIVE_LAYOUT);
- String exclude = oldIsRelativeLayout ? null : mOldType;
-
+ if (oldIsRelativeLayout) {
+ exclude.add(mOldType);
+ }
mClassNames = WrapInWizard.addLayouts(mProject, mOldType, mTypeCombo, exclude, false);
mTypeCombo.select(0);
import static com.android.ide.common.layout.LayoutConstants.FQCN_RADIO_BUTTON;
import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW;
import static com.android.ide.common.layout.LayoutConstants.RADIO_GROUP;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE;
import static com.android.sdklib.SdkConstants.CLASS_VIEW;
import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
import static com.android.sdklib.SdkConstants.FN_FRAMEWORK_LIBRARY;
import org.eclipse.swt.widgets.Text;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
@SuppressWarnings("restriction") // JDT model access for custom-view class lookup
class WrapInWizard extends VisualRefactoringWizard {
mUpdateReferences.setText("Update layout references");
mUpdateReferences.addSelectionListener(selectionListener);
- mClassNames = addLayouts(mProject, mOldType, mTypeCombo, null, true);
+ Set<String> exclude = Collections.singleton(VIEW_INCLUDE);
+ mClassNames = addLayouts(mProject, mOldType, mTypeCombo, exclude, true);
mTypeCombo.select(0);
setControl(composite);
}
}
- static List<String> addLayouts(IProject project, String oldType, Combo combo, String exclude,
- boolean addGestureOverlay) {
+ static List<String> addLayouts(IProject project, String oldType, Combo combo,
+ Set<String> exclude, boolean addGestureOverlay) {
List<String> classNames = new ArrayList<String>();
if (oldType.equals(FQCN_RADIO_BUTTON)) {
if (layoutDescriptors != null) {
for (ViewElementDescriptor d : layoutDescriptors) {
String className = d.getFullClassName();
- if (exclude == null || !exclude.equals(className)) {
+ if (exclude == null || !exclude.contains(className)) {
combo.add(d.getUiName());
classNames.add(className);
}
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.window.Window;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private Button mNewButton;
private String mCurrentResource;
private final IProject mProject;
+ private IInputValidator mInputValidator;
/**
* Creates a Resource Chooser dialog.
return mCurrentResource;
}
+ public void setInputValidator(IInputValidator inputValidator) {
+ mInputValidator = inputValidator;
+ }
+
@Override
protected void computeResult() {
Object[] elements = getSelectedElements();
mCurrentResource = ResourceHelper.getXmlString(mResourceType, item,
mSystemButton.getSelection());
+
+ if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) {
+ mCurrentResource = null;
+ }
}
}
});
}
+ @Override
+ protected void handleSelectionChanged() {
+ super.handleSelectionChanged();
+ if (mInputValidator != null) {
+ Object[] elements = getSelectedElements();
+ if (elements.length == 1 && elements[0] instanceof ResourceItem) {
+ ResourceItem item = (ResourceItem)elements[0];
+ String current = ResourceHelper.getXmlString(mResourceType, item,
+ mSystemButton.getSelection());
+ String error = mInputValidator.isValid(current);
+ IStatus status;
+ if (error != null) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error);
+ } else {
+ status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null);
+ }
+ updateStatus(status);
+ }
+ }
+ }
+
private String createNewValue(ResourceType type) {
// Show a name/value dialog entering the key name and the value
Shell shell = AdtPlugin.getDisplay().getActiveShell();
items = mFrameworkResources.getResourceItemsOfType(mResourceType);
}
+ if (items == null) {
+ items = Collections.emptyList();
+ }
+
ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]);
// sort the array
boolean isSystem = false;
String itemName = null;
- // Is this a system resource?
- // If not a system resource or if they are not available, this will be a project res.
- Matcher m = mSystemResourcePattern.matcher(resourceString);
- if (m.matches()) {
- itemName = m.group(1);
- isSystem = true;
- }
-
- if (!isSystem && itemName == null) {
- // Try to match project resource name
- m = mProjectResourcePattern.matcher(resourceString);
+ if (resourceString != null) {
+ // Is this a system resource?
+ // If not a system resource or if they are not available, this will be a project res.
+ Matcher m = mSystemResourcePattern.matcher(resourceString);
if (m.matches()) {
itemName = m.group(1);
+ isSystem = true;
+ }
+
+ if (!isSystem && itemName == null) {
+ // Try to match project resource name
+ m = mProjectResourcePattern.matcher(resourceString);
+ if (m.matches()) {
+ itemName = m.group(1);
+ }
}
}
fail("Not supported in tests yet");
return null;
}
+
+ public String displayIncludeSourceInput() {
+ fail("Not supported in tests yet");
+ return null;
+ }
}
public void testDummy() {
return child;
}
+ public void removeChild(INode node) {
+ int index = mChildren.indexOf(node);
+ if (index != -1) {
+ removeChild(index);
+ }
+ }
+
public boolean setAttribute(String uri, String localName, String value) {
mAttributes.put(uri + localName, new TestAttribute(uri, localName, value));
return true;
return "TestNode [fqn=" + mFqcn + ", infos=" + mAttributeInfos
+ ", attributes=" + mAttributes + ", bounds=" + mBounds + "]";
}
+
}
\ No newline at end of file
public void testSkip() throws Exception {
ViewMetadataRepository repository = ViewMetadataRepository.get();
- assertTrue(repository.getSkip("include"));
+ assertTrue(repository.getSkip("merge"));
assertFalse(repository.getSkip("android.widget.Button"));
}