blob: 0be4ed2f9b1abe13804a2c874abac40248cddf1e [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.intellij.android.designer.model;
import com.android.ide.common.rendering.api.ViewInfo;
import com.intellij.android.designer.designSurface.AndroidPasteFactory;
import com.intellij.designer.model.MetaManager;
import com.intellij.designer.model.MetaModel;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.model.RadLayout;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.XmlElementFactory;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.util.XmlUtil;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.android.SdkConstants.*;
/**
* Operations for a hierarchy of {@link com.intellij.android.designer.model.RadViewComponent} instances, such as adding, removing
* and moving components.
*/
public class RadComponentOperations {
private RadComponentOperations() {
// No state
}
public static RadViewComponent createComponent(@Nullable XmlTag tag, @NotNull MetaModel metaModel) throws Exception {
RadViewComponent component = (RadViewComponent)metaModel.getModel().newInstance();
assert component != null : tag;
component.setMetaModel(metaModel);
component.setTag(tag);
Class<RadLayout> layout = metaModel.getLayout();
if (layout == null) {
component.setLayout(RadViewLayout.INSTANCE);
}
else {
component.setLayout(layout.newInstance());
}
return component;
}
public static void moveComponent(final RadViewComponent container,
final RadViewComponent movedComponent,
@Nullable final RadViewComponent insertBefore)
throws Exception {
movedComponent.removeFromParent();
container.add(movedComponent, insertBefore);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
XmlTag xmlTag = movedComponent.getTag();
XmlTag parentTag = container.getTag();
XmlTag nextTag = insertBefore == null ? null : insertBefore.getTag();
XmlTag newXmlTag;
if (nextTag == null) {
newXmlTag = parentTag.addSubTag(xmlTag, false);
}
else {
newXmlTag = (XmlTag)parentTag.addBefore(xmlTag, nextTag);
}
xmlTag.delete();
movedComponent.updateTag(newXmlTag);
}
});
XmlFile xmlFile = RadModelBuilder.getXmlFile(container);
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(xmlFile.getProject());
Document document = psiDocumentManager.getDocument(xmlFile);
if (document != null) {
psiDocumentManager.commitDocument(document);
}
PropertyParser propertyParser = RadModelBuilder.getPropertyParser(container);
if (propertyParser != null) {
propertyParser.load(movedComponent);
}
}
public static void addComponent(RadViewComponent container, final RadViewComponent newComponent, @Nullable RadViewComponent insertBefore)
throws Exception {
container.add(newComponent, insertBefore);
addComponentTag(container.getTag(), newComponent, insertBefore == null ? null : insertBefore.getTag(), new Computable<String>() {
@Override
public String compute() {
String creation;
if (newComponent.getInitialPaletteItem() != null) {
creation = newComponent.getInitialPaletteItem().getCreation();
} else {
creation = newComponent.getMetaModel().getCreation();
}
return creation == null ? newComponent.getCreationXml() : creation;
}
});
PropertyParser propertyParser = RadModelBuilder.getPropertyParser(container);
if (propertyParser != null) {
propertyParser.load(newComponent);
if (!newComponent.getTag().isEmpty()) {
addComponent(newComponent, ViewsMetaManager.getInstance(newComponent.getTag().getProject()), propertyParser);
}
}
IdManager idManager = IdManager.get();
if (idManager.needsDefaultId(newComponent)) {
idManager.ensureIds(newComponent);
}
}
private static void addComponent(RadViewComponent parentComponent,
MetaManager metaManager,
PropertyParser propertyParser) throws Exception {
for (XmlTag tag : parentComponent.getTag().getSubTags()) {
MetaModel metaModel = metaManager.getModelByTag(tag.getName());
if (metaModel == null) {
metaModel = metaManager.getModelByTag(VIEW_TAG);
assert metaModel != null;
}
RadViewComponent component = createComponent(tag, metaModel);
parentComponent.add(component, null);
propertyParser.load(component);
addComponent(component, metaManager, propertyParser);
}
}
public static void pasteComponent(RadViewComponent container, RadViewComponent newComponent, @Nullable RadViewComponent insertBefore)
throws Exception {
container.add(newComponent, insertBefore);
PropertyParser propertyParser = RadModelBuilder.getPropertyParser(container);
if (propertyParser != null) {
pasteComponent(newComponent, container.getTag(), insertBefore == null ? null : insertBefore.getTag(), propertyParser);
IdManager.get().ensureIds(newComponent);
}
}
private static void pasteComponent(final RadViewComponent component,
XmlTag parentTag,
@Nullable XmlTag nextTag,
PropertyParser propertyParser) throws Exception {
addComponentTag(parentTag, component, nextTag, new Computable<String>() {
@Override
public String compute() {
Element pasteProperties = component.extractClientProperty(AndroidPasteFactory.KEY);
if (pasteProperties == null) {
return component.getMetaModel().getCreation();
}
StringBuilder builder = new StringBuilder();
builder.append("<").append(component.getMetaModel().getTag());
for (Object object : pasteProperties.getAttributes()) {
Attribute attribute = (Attribute)object;
builder.append(" ").append(attribute.getName()).append("=\"").append(attribute.getValue()).append("\"");
}
for (Object object : pasteProperties.getChildren()) {
Element element = (Element)object;
String namespace = element.getName();
for (Object child : element.getAttributes()) {
Attribute attribute = (Attribute)child;
builder.append(" ").append(namespace).append(":").append(attribute.getName()).append("=\"").append(attribute.getValue()).append(
"\"");
}
}
// TODO: Handle Android namespace properly if changed to something custom
if (builder.indexOf("android:layout_width=\"") == -1) {
builder.append(" android:layout_width=\"wrap_content\"");
}
if (builder.indexOf("android:layout_height=\"") == -1) {
builder.append(" android:layout_height=\"wrap_content\"");
}
return builder.append("/>").toString();
}
});
XmlTag xmlTag = component.getTag();
List<RadComponent> children = component.getChildren();
int size = children.size();
for (int i = 0; i < size; i++) {
RadViewComponent child = (RadViewComponent)children.get(i);
XmlTag nextChildTag = null;
if (i + 1 < size) {
nextChildTag = ((RadViewComponent)children.get(i + 1)).getTag();
}
pasteComponent(child, xmlTag, nextChildTag, propertyParser);
}
propertyParser.load(component);
}
public static void addComponentTag(final XmlTag parentTag,
final RadViewComponent component,
@Nullable final XmlTag nextTag,
final Computable<String> tagBuilder) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
Project project;
RadViewComponent root = null;
XmlFile xmlFile = null;
if (!checkTag(parentTag) && component.getParent() == component.getRoot()) {
root = (RadViewComponent)component.getParent();
xmlFile = RadModelBuilder.getXmlFile(root);
project = xmlFile.getProject();
}
else {
project = parentTag.getProject();
}
Language language = StdFileTypes.XML.getLanguage();
XmlTag xmlTag =
XmlElementFactory.getInstance(project).createTagFromText("\n" + tagBuilder.compute(), language);
if (checkTag(parentTag)) {
String namespacePrefix = parentTag.getPrefixByNamespace(ANDROID_URI);
// In the metadata the namespace prefix is hardcoded as "android"; convert to the current file's namespace
if (namespacePrefix != null && !ANDROID_NS_NAME.equals(namespacePrefix)) {
convertNamespacePrefix(xmlTag, namespacePrefix);
}
if (nextTag == null) {
xmlTag = parentTag.addSubTag(xmlTag, false);
}
else {
xmlTag = (XmlTag)parentTag.addBefore(xmlTag, nextTag);
}
}
else {
xmlTag.setAttribute(XMLNS_ANDROID, ANDROID_URI);
if (xmlFile != null) {
XmlDocument document = xmlFile.getDocument();
if (document != null) {
xmlTag = (XmlTag)document.add(xmlTag);
XmlUtil.expandTag(xmlTag);
XmlTag rootTag = document.getRootTag();
if (rootTag != null) {
root.setTag(rootTag);
}
}
}
}
component.setTag(xmlTag);
}
});
}
private static boolean checkTag(XmlTag tag) {
try {
return tag != null && tag.getFirstChild() != null && !(tag.getFirstChild() instanceof PsiErrorElement);
}
catch (Throwable e) {
return false;
}
}
private static void convertNamespacePrefix(XmlTag xmlTag, String namespacePrefix) {
for (XmlAttribute attribute : xmlTag.getAttributes()) {
if (ANDROID_NS_NAME.equals(attribute.getNamespacePrefix())) {
attribute.setName(namespacePrefix + ":" + attribute.getLocalName());
}
}
for (XmlTag subTag : xmlTag.getSubTags()) {
convertNamespacePrefix(subTag, namespacePrefix);
}
}
public static void deleteAttribute(RadComponent component, String name) {
deleteAttribute(((RadViewComponent)component).getTag(), name);
}
public static void deleteAttribute(XmlTag tag, String name) {
deleteAttribute(tag, name, ANDROID_URI);
}
public static void deleteAttribute(XmlTag tag, String name, String namespace) {
XmlAttribute attribute = tag.getAttribute(name, namespace);
if (attribute != null) {
attribute.delete();
}
}
public static void printTree(StringBuilder builder, RadComponent component, int level) {
for (int i = 0; i < level; i++) {
builder.append('\t');
}
builder.append(component).append(" | ").append(component.getLayout()).append(" | ").append(component.getMetaModel().getTag())
.append(" | ").append(component.getMetaModel().getTarget()).append(" = ").append(component.getChildren().size()).append("\n");
for (RadComponent childComponent : component.getChildren()) {
printTree(builder, childComponent, level + 1);
}
}
public static void printTree(StringBuilder builder, ViewInfo viewInfo, int level) {
for (int i = 0; i < level; i++) {
builder.append('\t');
}
builder.append(viewInfo.getClassName()).append(" | ");
try {
builder.append(viewInfo.getViewObject()).append(" | ");
}
catch (Throwable e) {
// ignored
}
try {
builder.append(viewInfo.getLayoutParamsObject()).append(" = ");
}
catch (Throwable e) {
// ignored
}
builder.append(viewInfo.getChildren().size()).append("\n");
for (ViewInfo childViewInfo : viewInfo.getChildren()) {
printTree(builder, childViewInfo, level + 1);
}
}
}