Root layout

This commit is contained in:
Alexander Lobas
2012-03-26 13:47:39 +04:00
parent c45d56b471
commit f790839c57
13 changed files with 360 additions and 70 deletions
@@ -24,6 +24,7 @@
<orderEntry type="module" module-name="ui-designer-new" />
<orderEntry type="module" module-name="dom-impl" />
<orderEntry type="module" module-name="dom-openapi" />
<orderEntry type="module" module-name="xml" />
</component>
</module>
@@ -40,6 +40,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlFile;
@@ -56,7 +57,6 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
/**
@@ -137,6 +137,8 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
parser.updateRootComponent(mySession, rootView);
RadViewComponent newRootComponent = parser.getRootComponent();
newRootComponent.setClientProperty(ModelParser.XML_FILE_KEY, myXmlFile);
PropertyParser propertyParser = new PropertyParser(myModule, myProfileAction.getProfileManager().getSelectedTarget());
newRootComponent.setClientProperty(PropertyParser.KEY, propertyParser);
propertyParser.loadRecursive(newRootComponent);
@@ -158,7 +160,10 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
final String layoutXmlText = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
@Override
public String compute() {
return myXmlFile.getText();
if (ModelParser.checkTag(myXmlFile.getRootTag())) {
return myXmlFile.getText();
}
return ModelParser.NO_ROOT_CONTENT;
}
});
createRenderer(layoutXmlText, new ThrowableRunnable<Throwable>() {
@@ -387,27 +392,4 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
myPSIChangeListener.start();
}
}
private static class RootView extends JComponent {
private int myX;
private int myY;
private BufferedImage myImage;
public RootView(BufferedImage image, int x, int y) {
myX = x;
myY = y;
setImage(image);
}
public void setImage(BufferedImage image) {
myImage = image;
setBounds(myX, myY, image.getWidth(), image.getHeight());
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(myImage, 0, 0, null);
}
}
}
@@ -0,0 +1,88 @@
/*
* 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.designSurface;
import com.intellij.android.designer.model.ModelParser;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.designer.designSurface.EditOperation;
import com.intellij.designer.designSurface.FeedbackLayer;
import com.intellij.designer.designSurface.OperationContext;
import com.intellij.designer.designSurface.feedbacks.AlphaComponent;
import com.intellij.designer.model.RadComponent;
import javax.swing.*;
import java.awt.*;
import java.util.Collections;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class DropToOperation implements EditOperation {
private final RadViewComponent myContainer;
private final OperationContext myContext;
private List<RadComponent> myComponents;
private JComponent myFeedback;
public DropToOperation(RadViewComponent container, OperationContext context) {
myContainer = container;
myContext = context;
}
@Override
public void setComponent(RadComponent component) {
myComponents = Collections.singletonList(component);
}
@Override
public void setComponents(List<RadComponent> components) {
myComponents = components;
}
@Override
public void showFeedback() {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
if (myFeedback == null) {
myFeedback = new AlphaComponent(Color.green);
layer.add(myFeedback);
myFeedback.setBounds(myContainer.getBounds(layer));
layer.repaint();
}
}
@Override
public void eraseFeedback() {
if (myFeedback != null) {
FeedbackLayer layer = myContext.getArea().getFeedbackLayer();
layer.remove(myFeedback);
layer.repaint();
myFeedback = null;
}
}
@Override
public boolean canExecute() {
return true;
}
@Override
public void execute() throws Exception {
for (RadComponent component : myComponents) {
ModelParser.addComponent(myContainer, (RadViewComponent)component, null);
}
}
}
@@ -0,0 +1,75 @@
/*
* 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.designSurface;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class RootView extends JComponent {
private int myX;
private int myY;
private BufferedImage myImage;
private List<EmptyRegion> myEmptyRegions;
public RootView(BufferedImage image, int x, int y) {
myX = x;
myY = y;
setImage(image);
}
public void setImage(BufferedImage image) {
myImage = image;
myEmptyRegions = new ArrayList<EmptyRegion>();
setBounds(myX, myY, image.getWidth(), image.getHeight());
}
public void addEmptyRegion(int x, int y, int width, int height) {
if (new Rectangle(0, 0, myImage.getWidth(), myImage.getHeight()).contains(x, y)) {
EmptyRegion r = new EmptyRegion();
r.myX = x;
r.myY = y;
r.myWidth = width;
r.myHeight = height;
r.myColor = new Color(~myImage.getRGB(x, y));
myEmptyRegions.add(r);
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(myImage, 0, 0, null);
for (EmptyRegion r : myEmptyRegions) {
g.setColor(r.myColor);
g.fillRect(r.myX, r.myY, r.myWidth, r.myHeight);
}
}
private static class EmptyRegion {
public Color myColor;
public int myX;
public int myY;
public int myWidth;
public int myHeight;
}
}
@@ -0,0 +1,36 @@
/*
* 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.designSurface;
import com.intellij.android.designer.model.ModelParser;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.designer.componentTree.TreeEditOperation;
import com.intellij.designer.designSurface.OperationContext;
import com.intellij.designer.model.RadComponent;
/**
* @author Alexander Lobas
*/
public class TreeDropToOperation extends TreeEditOperation {
public TreeDropToOperation(RadComponent host, OperationContext context) {
super(host, context);
}
@Override
protected void execute(RadComponent insertBefore) throws Exception {
ModelParser.addComponent((RadViewComponent)myHost, (RadViewComponent)myComponents.get(0), (RadViewComponent)insertBefore);
}
}
@@ -17,6 +17,7 @@ package com.intellij.android.designer.model;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ViewInfo;
import com.intellij.android.designer.designSurface.RootView;
import com.intellij.designer.model.MetaManager;
import com.intellij.designer.model.MetaModel;
import com.intellij.designer.model.RadComponent;
@@ -26,10 +27,14 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.XmlElementFactory;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
@@ -38,6 +43,13 @@ import java.util.List;
* @author Alexander Lobas
*/
public class ModelParser extends XmlRecursiveElementVisitor {
public static final String NO_ROOT_CONTENT =
"<?xml version=\"1.0\" encoding=\"utf-8\"?><LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:layout_width=\"fill_parent\" android:layout_height=\"fill_parent\" android:orientation=\"vertical\"></LinearLayout>";
public static final String XML_FILE_KEY = "XML_FILE";
private static final int EMPTY_COMPONENT_SIZE = 5;
private final MetaManager myMetaManager;
private final XmlFile myXmlFile;
private RadViewComponent myRootComponent;
@@ -61,11 +73,11 @@ public class ModelParser extends XmlRecursiveElementVisitor {
@Override
public String compute() {
XmlTag root = myXmlFile.getRootTag();
if (root != null) {
if (checkTag(root)) {
root.accept(ModelParser.this);
return myXmlFile.getText();
}
return myXmlFile.getText();
return NO_ROOT_CONTENT;
}
});
}
@@ -111,19 +123,58 @@ public class ModelParser extends XmlRecursiveElementVisitor {
return component;
}
public static void addComponent(RadViewComponent container, RadViewComponent newComponent, @Nullable RadViewComponent insertBefore)
throws Exception {
newComponent.setParent(container);
List<RadComponent> children = container.getChildren();
if (insertBefore == null) {
children.add(newComponent);
}
else {
children.add(children.indexOf(insertBefore), newComponent);
}
setComponentTag(container.getTag(), newComponent, insertBefore == null ? null : insertBefore.getTag());
PropertyParser propertyParser = container.getRoot().getClientProperty(PropertyParser.KEY);
propertyParser.load(newComponent);
}
public static void setComponentTag(final XmlTag parentTag, final RadViewComponent component, final XmlTag nextTag) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
Language language = StdFileTypes.XML.getLanguage();
XmlTag xmlTag =
XmlElementFactory.getInstance(parentTag.getProject()).createTagFromText(component.getMetaModel().getCreation(), language);
Project project;
RadViewComponent root = null;
XmlFile xmlFile = null;
if (nextTag == null) {
xmlTag = parentTag.addSubTag(xmlTag, false);
if (!checkTag(parentTag) && component.getParent() == component.getRoot()) {
root = (RadViewComponent)component.getParent();
xmlFile = root.getClientProperty(XML_FILE_KEY);
project = xmlFile.getProject();
}
else {
xmlTag = (XmlTag)parentTag.addBefore(xmlTag, nextTag);
project = parentTag.getProject();
}
Language language = StdFileTypes.XML.getLanguage();
XmlTag xmlTag =
XmlElementFactory.getInstance(project).createTagFromText("\n" + component.getMetaModel().getCreation(), language);
if (checkTag(parentTag)) {
if (nextTag == null) {
xmlTag = parentTag.addSubTag(xmlTag, false);
}
else {
xmlTag = (XmlTag)parentTag.addBefore(xmlTag, nextTag);
}
}
else {
xmlTag.setAttribute("xmlns:android", "http://schemas.android.com/apk/res/android");
xmlTag = (XmlTag)xmlFile.getDocument().add(xmlTag);
root.setTag(xmlFile.getDocument().getRootTag());
XmlUtil.expandTag(xmlTag);
}
component.setTag(xmlTag);
@@ -131,6 +182,15 @@ public class ModelParser extends XmlRecursiveElementVisitor {
});
}
public static boolean checkTag(XmlTag tag) {
try {
return tag != null && tag.getFirstChild() != null && !(tag.getFirstChild() instanceof PsiErrorElement) && tag.getProject() != null;
}
catch (Throwable e) {
return false;
}
}
//////////////////////////////////////////////////////////////////////////////////////////
//
//
@@ -145,27 +205,26 @@ public class ModelParser extends XmlRecursiveElementVisitor {
return myLayoutXmlText;
}
public void updateRootComponent(RenderSession session, JComponent nativeComponent) throws Exception {
RadViewComponent rootComponent = myRootComponent;
if (session.getRootViews().size() == 1) {
RadViewComponent newRootComponent = createComponent(myXmlFile.getRootTag(), myMetaManager.getModelByTag("<root>"));
rootComponent.setParent(newRootComponent);
newRootComponent.getChildren().add(rootComponent);
myRootComponent = rootComponent = newRootComponent;
public void updateRootComponent(RenderSession session, RootView nativeComponent) throws Exception {
if (myRootComponent == null) {
myRootComponent = createComponent(myXmlFile.getRootTag(), myMetaManager.getModelByTag("<root>"));
}
else if (session.getRootViews().size() == 1) {
RadViewComponent rootComponent = myRootComponent;
myRootComponent = createComponent(myXmlFile.getRootTag(), myMetaManager.getModelByTag("<root>"));
myRootComponent.getChildren().add(rootComponent);
rootComponent.setParent(myRootComponent);
}
updateRootComponent(rootComponent, session, nativeComponent);
updateRootComponent(myRootComponent, session, nativeComponent);
}
public static void updateRootComponent(RadViewComponent rootComponent,
RenderSession session,
JComponent nativeComponent) {
RootView nativeComponent) {
List<ViewInfo> views = session.getRootViews();
int size = views.size();
List<RadComponent> children = rootComponent.getChildren();
int size = children.size();
for (int i = 0; i < size; i++) {
updateComponent((RadViewComponent)children.get(i), views.get(i), nativeComponent, 0, 0);
}
@@ -176,7 +235,7 @@ public class ModelParser extends XmlRecursiveElementVisitor {
private static void updateComponent(RadViewComponent component,
ViewInfo view,
JComponent nativeComponent,
RootView nativeComponent,
int parentX,
int parentY) {
component.setViewInfo(view);
@@ -184,11 +243,18 @@ public class ModelParser extends XmlRecursiveElementVisitor {
int left = parentX + view.getLeft();
int top = parentY + view.getTop();
component.setBounds(left, top, view.getRight() - view.getLeft(), view.getBottom() - view.getTop());
int width = view.getRight() - view.getLeft();
int height = view.getBottom() - view.getTop();
if (width < EMPTY_COMPONENT_SIZE && height < EMPTY_COMPONENT_SIZE) {
nativeComponent.addEmptyRegion(left, top, EMPTY_COMPONENT_SIZE, EMPTY_COMPONENT_SIZE);
}
component.setBounds(left, top, Math.max(width, EMPTY_COMPONENT_SIZE), Math.max(height, EMPTY_COMPONENT_SIZE));
List<ViewInfo> views = view.getChildren();
List<RadComponent> children = component.getChildren();
int size = views.size();
int size = children.size();
for (int i = 0; i < size; i++) {
updateComponent((RadViewComponent)children.get(i), views.get(i), nativeComponent, left, top);
@@ -40,22 +40,7 @@ public class RadLinearLayout extends RadViewLayout {
}
private void addNewComponent(RadViewComponent newComponent, @Nullable RadViewComponent insertBefore) throws Exception {
RadViewComponent container = (RadViewComponent)myContainer;
newComponent.setParent(container);
List<RadComponent> children = container.getChildren();
if (insertBefore == null) {
children.add(newComponent);
}
else {
children.add(children.indexOf(insertBefore), newComponent);
}
ModelParser
.setComponentTag(container.getTag(), newComponent, insertBefore == null ? null : insertBefore.getTag());
PropertyParser propertyParser = container.getRoot().getClientProperty(PropertyParser.KEY);
propertyParser.load(newComponent);
ModelParser.addComponent((RadViewComponent)myContainer, newComponent, insertBefore);
}
private class TreeCreateOperation extends TreeEditOperation {
@@ -0,0 +1,38 @@
/*
* 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.intellij.android.designer.designSurface.DropToOperation;
import com.intellij.android.designer.designSurface.TreeDropToOperation;
import com.intellij.designer.designSurface.EditOperation;
import com.intellij.designer.designSurface.OperationContext;
/**
* @author Alexander Lobas
*/
public class RadRootViewLayout extends RadViewLayout {
@Override
public EditOperation processChildOperation(OperationContext context) {
if (myContainer.getChildren().isEmpty() && (context.isCreate() || context.isPaste()) && context.getComponents().size() == 1) {
if (context.isTree()) {
return new TreeDropToOperation(myContainer, context);
}
return new DropToOperation((RadViewComponent)myContainer, context);
}
return null;
}
}
@@ -5,15 +5,17 @@
<!-- ================= Roots ================= -->
<meta model="com.intellij.android.designer.model.RadViewComponent"
layout="com.intellij.android.designer.model.RadViewLayout"
tag="&lt;root&gt;">
layout="com.intellij.android.designer.model.RadRootViewLayout"
tag="&lt;root&gt;"
delete="false">
<palette title="Device Screen" icon="/com/intellij/android/designer/icons/DeviceScreen.png"/>
</meta>
<meta model="com.intellij.android.designer.model.RadViewComponent"
layout="com.intellij.android.designer.model.RadViewLayout"
tag="merge">
tag="merge"
delete="false">
<palette title="Device Screen (#merge)" icon="/com/intellij/android/designer/icons/DeviceScreen.png"/>
</meta>
@@ -83,6 +83,9 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen
myContentPanel.addTab("Project", myProjectPanel.myComponent);
myContentPanel.addTab("System", mySystemPanel.myComponent);
myProjectPanel.myTreeBuilder.expandAll(null);
mySystemPanel.myTreeBuilder.expandAll(null);
myContentPanel.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
@@ -92,6 +92,11 @@ public abstract class MetaManager {
meta.setLayout((Class<RadLayout>)classLoader.loadClass(layout));
}
String delete = element.getAttributeValue("delete");
if (delete != null) {
meta.setDelete(Boolean.parseBoolean(delete));
}
Element presentation = element.getChild("presentation");
if (presentation != null) {
meta.setPresentation(presentation.getAttributeValue("title"), presentation.getAttributeValue("icon"));
@@ -36,6 +36,7 @@ public class MetaModel {
private String myIconPath;
private Icon myIcon;
private String myCreation;
private boolean myDelete = true;
private List<String> myNormalProperties = Collections.emptyList();
private List<String> myImportantProperties = Collections.emptyList();
private List<String> myExpertProperties = Collections.emptyList();
@@ -75,6 +76,14 @@ public class MetaModel {
myCreation = creation;
}
public boolean canDelete() {
return myDelete;
}
public void setDelete(boolean delete) {
myDelete = delete;
}
public String getTitle() {
return myTitle;
}
@@ -175,7 +175,7 @@ public abstract class RadComponent {
//////////////////////////////////////////////////////////////////////////////////////////
public boolean canDelete() {
return true;
return myMetaModel == null || myMetaModel.canDelete();
}
public void delete() throws Exception {