Handle external PSI changes

This commit is contained in:
Alexander Lobas
2012-02-22 23:27:54 +04:00
parent 9551cfac43
commit 76fc02daf0
10 changed files with 349 additions and 99 deletions
@@ -15,14 +15,15 @@
*/
package com.intellij.android.designer;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlFile;
import org.jdom.Element;
import org.jetbrains.android.dom.layout.LayoutDomFileDescription;
@@ -34,15 +35,16 @@ import org.jetbrains.annotations.NotNull;
*/
public final class AndroidDesignerEditorProvider implements FileEditorProvider, DumbAware {
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
Document document = FileDocumentManager.getInstance().getDocument(file);
if (document != null) {
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
return psiFile instanceof XmlFile &&
AndroidFacet.getInstance(psiFile) != null &&
LayoutDomFileDescription.isLayoutFile((XmlFile)psiFile);
}
return false;
public boolean accept(final @NotNull Project project, final @NotNull VirtualFile file) {
PsiFile psiFile = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
@Override
public PsiFile compute() {
return PsiManager.getInstance(project).findFile(file);
}
});
return psiFile instanceof XmlFile &&
AndroidFacet.getInstance(psiFile) != null &&
LayoutDomFileDescription.isLayoutFile((XmlFile)psiFile);
}
@NotNull
@@ -24,6 +24,7 @@ import com.android.resources.UiMode;
import com.android.sdklib.IAndroidTarget;
import com.intellij.android.designer.componentTree.AndroidTreeDecorator;
import com.intellij.android.designer.model.RadViewComponent;
import com.intellij.designer.DesignerToolWindowManager;
import com.intellij.designer.componentTree.TreeComponentDecorator;
import com.intellij.designer.designSurface.*;
import com.intellij.designer.designSurface.selection.DirectionResizePoint;
@@ -33,11 +34,28 @@ import com.intellij.designer.designSurface.tools.ComponentCreationFactory;
import com.intellij.designer.designSurface.tools.CreationTool;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.utils.Position;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Alarm;
import com.intellij.util.ThrowableRunnable;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.uipreview.*;
import org.jetbrains.android.util.AndroidSdkNotConfiguredException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
@@ -50,6 +68,7 @@ import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@@ -57,13 +76,43 @@ import java.util.List;
* @author Alexander Lobas
*/
public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
private static final Object RENDERING_LOCK = new Object();
private final TreeComponentDecorator myTreeDecorator = new AndroidTreeDecorator();
private final XmlFile myXmlFile;
private final ExternalPSIChangeListener myPSIChangeListener;
private RenderSession mySession;
public AndroidDesignerEditorPanel(@NotNull Module module, @NotNull VirtualFile file) {
super(module, file);
myXmlFile = (XmlFile)ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
@Override
public PsiFile compute() {
return PsiManager.getInstance(myModule.getProject()).findFile(myFile);
}
});
myPSIChangeListener = new ExternalPSIChangeListener(this, myXmlFile, 100, new Runnable() {
@Override
public void run() {
reparseFile();
}
});
// TODO: work over activate() / deactivate()
myPSIChangeListener.start();
// TODO: save last parse result (screen image, component info) to project output
// TODO: and use for next open editor (no wait first long init Android RenderLib)
try {
parseFile();
showDesignerCard();
}
catch (Throwable e) {
showError("Parse error: ", e);
}
// TODO: temp code
myGlassLayer.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent event) {
@@ -78,93 +127,92 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
}
}
});
}
// TODO: (profile|device|target|...|theme) panel
// TODO: use platform DOM
// (temp code)
private void reparseFile() {
try {
AndroidPlatform platform = AndroidPlatform.getInstance(module);
IAndroidTarget target = platform.getTarget();
AndroidFacet facet = AndroidFacet.getInstance(module);
LayoutDeviceManager layoutDeviceManager = new LayoutDeviceManager();
layoutDeviceManager.loadDevices(platform.getSdk());
LayoutDevice layoutDevice = layoutDeviceManager.getCombinedList().get(0);
LayoutDeviceConfiguration deviceConfiguration = layoutDevice.getConfigurations().get(0);
FolderConfiguration config = new FolderConfiguration();
config.set(deviceConfiguration.getConfiguration());
config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL));
config.setNightModeQualifier(new NightModeQualifier(NightMode.NIGHT));
config.setLanguageQualifier(new LanguageQualifier());
config.setRegionQualifier(new RegionQualifier());
float xdpi = deviceConfiguration.getDevice().getXDpi();
float ydpi = deviceConfiguration.getDevice().getYDpi();
ThemeData theme = new ThemeData("Theme", false);
String layoutXmlText = new String(file.contentsToByteArray());
synchronized (RENDERING_LOCK) {
mySession = RenderUtil.createRenderSession(module.getProject(), layoutXmlText, file, target, facet, config, xdpi, ydpi, theme);
}
InputStream stream = file.getInputStream();
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(stream, new DefaultHandler() {
RadViewComponent myComponent;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
myComponent = new RadViewComponent(myComponent, qName);
if (myRootComponent == null) {
myRootComponent = myComponent;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (myComponent != null) {
myComponent = (RadViewComponent)myComponent.getParent();
}
}
});
stream.close();
Result result = mySession.getResult();
if (!result.isSuccess()) {
Throwable exception = result.getException();
if (exception != null) {
throw exception;
}
else {
throw new Exception("No session result");
}
}
RootView rootView = new RootView(mySession.getImage(), 30, 20);
updateRootComponent(mySession.getRootViews(), rootView);
JPanel rootPanel = new JPanel(null);
rootPanel.setBackground(Color.WHITE);
rootPanel.add(rootView);
myLayeredPane.add(rootPanel, LAYER_COMPONENT);
myToolProvider.loadDefaultTool();
mySurfaceArea.deselectAll();
parseFile();
showDesignerCard();
myLayeredPane.repaint();
DesignerToolWindowManager.getInstance(myModule.getProject()).refresh();
}
catch (Throwable e) {
showError("Parse error: ", e);
}
}
private void updateRootComponent(List<ViewInfo> views, JComponent nativeComponent) {
RadViewComponent rootComponent = (RadViewComponent)myRootComponent;
private void parseFile() throws Throwable {
final RadViewComponent[] rootComponents = new RadViewComponent[1];
final String layoutXmlText = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
RadViewComponent myComponent;
@Override
public String compute() {
XmlTag root = myXmlFile.getRootTag();
if (root != null) {
root.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlTag(XmlTag tag) {
myComponent = new RadViewComponent(myComponent, tag.getName());
if (rootComponents[0] == null) {
rootComponents[0] = myComponent;
}
super.visitXmlTag(tag);
myComponent = (RadViewComponent)myComponent.getParent();
}
});
}
return myXmlFile.getText();
}
});
// TODO: run in background
createRenderer(layoutXmlText);
Result result = mySession.getResult();
if (!result.isSuccess()) {
Throwable exception = result.getException();
if (exception != null) {
throw exception;
}
else {
throw new Exception("No session result");
}
}
RootView rootView = new RootView(mySession.getImage(), 30, 20);
updateRootComponent(rootComponents, mySession.getRootViews(), rootView);
JPanel rootPanel = new JPanel(null);
rootPanel.setBackground(Color.WHITE);
rootPanel.add(rootView);
if (myRootComponent != null) {
myLayeredPane.remove(((RadViewComponent)myRootComponent).getNativeComponent().getParent());
}
removeNativeRoot();
myRootComponent = rootComponents[0];
myLayeredPane.add(rootPanel, LAYER_COMPONENT);
}
private void removeNativeRoot() {
if (myRootComponent != null) {
myLayeredPane.remove(((RadViewComponent)myRootComponent).getNativeComponent().getParent());
}
}
private static void updateRootComponent(RadViewComponent[] rootComponents, List<ViewInfo> views, JComponent nativeComponent) {
RadViewComponent rootComponent = rootComponents[0];
int size = views.size();
if (size == 1) {
@@ -174,7 +222,7 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
updateComponent(rootComponent, views.get(0), nativeComponent, 0, 0);
myRootComponent = rootComponent = newRootComponent;
rootComponents[0] = rootComponent = newRootComponent;
}
else {
List<RadComponent> children = rootComponent.getChildren();
@@ -189,6 +237,7 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
private static void updateComponent(RadViewComponent component, ViewInfo view, JComponent nativeComponent, int parentX, int parentY) {
component.setNativeComponent(nativeComponent);
//System.out.println(view.getClassName() +" = " + mySession.getDefaultProperties(view.getViewObject()));
int left = parentX + view.getLeft();
int top = parentY + view.getTop();
@@ -203,6 +252,48 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel {
}
}
private void createRenderer(String layoutXmlText) throws Exception {
// TODO: (profile|device|target|...|theme) panel
AndroidPlatform platform = AndroidPlatform.getInstance(myModule);
IAndroidTarget target = platform.getTarget();
AndroidFacet facet = AndroidFacet.getInstance(myModule);
LayoutDeviceManager layoutDeviceManager = new LayoutDeviceManager();
layoutDeviceManager.loadDevices(platform.getSdk());
LayoutDevice layoutDevice = layoutDeviceManager.getCombinedList().get(0);
LayoutDeviceConfiguration deviceConfiguration = layoutDevice.getConfigurations().get(0);
FolderConfiguration config = new FolderConfiguration();
config.set(deviceConfiguration.getConfiguration());
config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL));
config.setNightModeQualifier(new NightModeQualifier(NightMode.NIGHT));
config.setLanguageQualifier(new LanguageQualifier());
config.setRegionQualifier(new RegionQualifier());
float xdpi = deviceConfiguration.getDevice().getXDpi();
float ydpi = deviceConfiguration.getDevice().getYDpi();
ThemeData theme = new ThemeData("Theme", false);
mySession = RenderUtil.createRenderSession(myModule.getProject(), layoutXmlText, myFile, target, facet, config, xdpi, ydpi, theme);
}
@Override
public void showError(@NonNls String message, Throwable e) {
removeNativeRoot();
super.showError(message, e);
}
@Override
public void dispose() {
myPSIChangeListener.stop();
super.dispose();
mySession = null;
}
@Override
public TreeComponentDecorator getTreeDecorator() {
return myTreeDecorator;
@@ -0,0 +1,106 @@
/*
* 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.openapi.application.ModalityState;
import com.intellij.psi.*;
import com.intellij.util.Alarm;
import javax.swing.*;
/**
* @author Alexander Lobas
*/
public class ExternalPSIChangeListener extends PsiTreeChangeAdapter {
private final Alarm myAlarm = new Alarm();
private final JComponent myComponent;
private final PsiFile myFile;
private final int myDelayMillis;
private final Runnable myRunnable;
private volatile boolean myRunState;
public ExternalPSIChangeListener(JComponent component, PsiFile file, int delayMillis, Runnable runnable) {
myComponent = component;
myFile = file;
myDelayMillis = delayMillis;
myRunnable = runnable;
}
public void start() {
if (!myRunState) {
myRunState = true;
PsiManager.getInstance(myFile.getProject()).addPsiTreeChangeListener(this);
}
}
public void stop() {
if (myRunState) {
myRunState = false;
PsiManager.getInstance(myFile.getProject()).removePsiTreeChangeListener(this);
myAlarm.cancelAllRequests();
}
}
private void update(PsiTreeChangeEvent event) {
if (myRunState && myFile == event.getFile()) {
myAlarm.cancelAllRequests();
myAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (myRunState) {
myRunnable.run();
}
}
}, myDelayMillis, ModalityState.stateForComponent(myComponent));
}
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// PSI
//
//////////////////////////////////////////////////////////////////////////////////////////
@Override
public void childAdded(PsiTreeChangeEvent event) {
update(event);
}
@Override
public void childRemoved(PsiTreeChangeEvent event) {
update(event);
}
@Override
public void childReplaced(PsiTreeChangeEvent event) {
update(event);
}
@Override
public void childMoved(PsiTreeChangeEvent event) {
update(event);
}
@Override
public void childrenChanged(PsiTreeChangeEvent event) {
update(event);
}
@Override
public void propertyChanged(PsiTreeChangeEvent event) {
update(event);
}
}
@@ -70,6 +70,10 @@ public class RadViewComponent extends RadComponent {
myBounds.setBounds(x, y, width, height);
}
public Component getNativeComponent() {
return myNativeComponent;
}
public void setNativeComponent(Component nativeComponent) {
myNativeComponent = nativeComponent;
}
@@ -72,10 +72,12 @@ public abstract class DesignerEditor extends UserDataHolderBase implements FileE
@Override
public void selectNotify() {
myDesignerPanel.activate();
}
@Override
public void deselectNotify() {
myDesignerPanel.deactivate();
}
@Override
@@ -113,6 +113,12 @@ public final class DesignerToolWindowManager implements ProjectComponent {
return project.getComponent(DesignerToolWindowManager.class);
}
public void refresh() {
if (myTreeBuilder != null) {
myTreeBuilder.queueUpdate();
}
}
private static DesignerEditorPanel getDesigner(FileEditor editor) {
if (editor instanceof DesignerEditor) {
DesignerEditor designerEditor = (DesignerEditor)editor;
@@ -19,11 +19,11 @@ import com.intellij.designer.designSurface.*;
import com.intellij.designer.designSurface.tools.InputTool;
import com.intellij.designer.model.RadComponent;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.ArrayList;
@@ -69,8 +69,7 @@ public final class TreeEditableArea implements EditableArea, FeedbackTreeLayer {
@Override
public void select(@NotNull RadComponent component) {
myTreeBuilder.queueUpdate();
myTreeBuilder.select(component);
setRawSelection(component);
}
@Override
@@ -92,9 +91,23 @@ public final class TreeEditableArea implements EditableArea, FeedbackTreeLayer {
setRawSelection(components);
}
private void setRawSelection(Collection<RadComponent> components) {
@Override
public void deselectAll() {
setRawSelection(null);
}
private void setRawSelection(@Nullable Object value) {
myTreeBuilder.queueUpdate();
myTreeBuilder.select(components.toArray(), null);
if (value == null) {
myTreeBuilder.select(ArrayUtil.EMPTY_OBJECT_ARRAY, null);
}
else if (value instanceof RadComponent) {
myTreeBuilder.select(value, null);
}
else {
myTreeBuilder.select(((Collection)value).toArray(), null);
}
}
private Collection<RadComponent> getRawSelection() {
@@ -96,6 +96,12 @@ public abstract class ComponentEditableArea implements EditableArea {
fireSelectionChanged();
}
@Override
public void deselectAll() {
mySelection = new ArrayList<RadComponent>();
fireSelectionChanged();
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// Visual
@@ -15,6 +15,7 @@
*/
package com.intellij.designer.designSurface;
import com.intellij.designer.DesignerToolWindowManager;
import com.intellij.designer.componentTree.TreeComponentDecorator;
import com.intellij.designer.designSurface.tools.InputTool;
import com.intellij.designer.designSurface.tools.SelectionTool;
@@ -22,12 +23,15 @@ import com.intellij.designer.designSurface.tools.ToolProvider;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.model.RadComponentVisitor;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ThrowableRunnable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,8 +43,6 @@ import java.awt.*;
* @author Alexander Lobas
*/
public abstract class DesignerEditorPanel extends JPanel implements DataProvider {
private final CardLayout myLayout = new CardLayout();
protected static final Integer LAYER_COMPONENT = JLayeredPane.DEFAULT_LAYER;
protected static final Integer LAYER_STATIC_DECORATION = JLayeredPane.POPUP_LAYER;
protected static final Integer LAYER_DECORATION = JLayeredPane.DRAG_LAYER;
@@ -50,6 +52,12 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
protected static final Integer LAYER_INPLACE_EDITING = LAYER_BUTTONS + 100;
@NonNls private final static String DESIGNER_CARD = "designer";
@NonNls private final static String ERROR_CARD = "error";
@NotNull protected final Module myModule;
@NotNull protected final VirtualFile myFile;
private final CardLayout myLayout = new CardLayout();
private JPanel myDesignerCard;
private CaptionPanel myHorizontalCaption;
private CaptionPanel myVerticalCaption;
@@ -58,18 +66,22 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
protected GlassLayer myGlassLayer;
private DecorationLayer myDecorationLayer;
private FeedbackLayer myFeedbackLayer;
protected ToolProvider myToolProvider;
protected EditableArea mySurfaceArea;
@NonNls private final static String ERROR_CARD = "error";
private JLabel myErrorLabel;
protected RadComponent myRootComponent;
public DesignerEditorPanel(@NotNull Module module, @NotNull VirtualFile file) {
myModule = module;
myFile = file;
setLayout(myLayout);
createDesignerCard();
createErrorCard();
myToolProvider.loadDefaultTool();
}
@@ -190,16 +202,23 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
@Nullable
protected abstract EditOperation processRootOperation(OperationContext context);
public final void showError(@NonNls String message, Throwable e) {
public void showError(@NonNls String message, Throwable e) {
myRootComponent = null;
myErrorLabel.setText(message + e.toString());
myLayout.show(this, ERROR_CARD);
DesignerToolWindowManager.getInstance(myModule.getProject()).refresh();
repaint();
if (ApplicationManagerEx.getApplicationEx().isInternal()) {
e.printStackTrace();
}
}
public void activate() {
}
public void deactivate() {
}
@Override
public Object getData(@NonNls String dataId) {
return null; //To change body of implemented methods use File | Settings | File Templates.
@@ -51,6 +51,7 @@ public interface EditableArea {
void setSelection(@NotNull List<RadComponent> components);
void deselectAll();
//////////////////////////////////////////////////////////////////////////////////////////
//
// Visual