OSDN Git Service

Open included layout on double click
authorTor Norbye <tnorbye@google.com>
Mon, 22 Nov 2010 05:37:20 +0000 (21:37 -0800)
committerTor Norbye <tnorbye@google.com>
Tue, 23 Nov 2010 23:51:38 +0000 (15:51 -0800)
On double click, if the clicked area originates from an <include>'ed
XML file, open the included layout in the editor. Also opens
files in the @android: namespace if available, for example if you
inculde @android:layout/select_dialog_multichoice.

Change-Id: I215c411257717f7b97e7b0ee1d5498c318cdb04d

eclipse/dictionary.txt
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java

index b9356c0..20ab93c 100644 (file)
@@ -103,6 +103,7 @@ preload
 preloads
 primordial
 printf
+programmatic
 programmatically
 proguard
 proxies
@@ -110,11 +111,13 @@ proxy
 recompilation
 rect
 redo
+refactor
 regexp
 registry
 remap
 reparse
 reparses
+rescales
 residual
 scrollable
 scrollbar
@@ -145,6 +148,7 @@ undescribed
 uninstall
 uninstallation
 uninstalling
+unset
 upcoming
 uri
 url
index a4d528c..a5d772c 100644 (file)
@@ -43,7 +43,7 @@ public class TableLayoutRule extends LinearLayoutRule {
     }
 
     @Override
-    public void onChildInserted(INode node, INode parent, InsertType insertType) {
+    public void onChildInserted(INode child, INode parent, InsertType insertType) {
         // Overridden to inhibit the setting of layout_width/layout_height since
         // it should always be match_parent
     }
index 7dc9169..74e86a0 100644 (file)
@@ -103,6 +103,15 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
     }
 
     /**
+     * Returns the {@link GraphicalEditorPart} associated with this editor
+     *
+     * @return the {@link GraphicalEditorPart} associated with this editor
+     */
+    public GraphicalEditorPart getGraphicalEditor() {
+        return mGraphicalEditor;
+    }
+
+    /**
      * @return The root node of the UI element hierarchy
      */
     @Override
index 2021533..903e030 100644 (file)
@@ -47,6 +47,13 @@ public final class LayoutDescriptors implements IDescriptorProvider {
      */
     public static final String VIEW_INCLUDE = "include";      //$NON-NLS-1$
 
+    /**
+     * The attribute name of the include tag's url naming the resource to be inserted
+     * <p>
+     * <b>NOTE</b>: The layout attribute is NOT in the Android namespace!
+     */
+    public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$
+
     // Public attributes names, attributes descriptors and elements descriptors
     public static final String ID_ATTR = "id"; //$NON-NLS-1$
 
@@ -293,7 +300,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
                 null, //elementXmlName
                 null, //nsUri
                 new AttributeInfo(
-                        "layout",       //$NON-NLS-1$
+                        ATTR_LAYOUT,
                         new Format[] { Format.REFERENCE } ),
                 true,  //required
                 null); //overrides
index 303c58d..cc2c489 100755 (executable)
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
 import com.android.ide.common.api.Rect;
 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
@@ -28,6 +29,7 @@ import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.ui.views.properties.IPropertyDescriptor;
 import org.eclipse.ui.views.properties.IPropertySheetPage;
 import org.eclipse.ui.views.properties.IPropertySource;
+import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
 import java.util.ArrayList;
@@ -370,4 +372,33 @@ public class CanvasViewInfo implements IPropertySource {
         return e;
     }
 
+    /**
+     * Returns the layout url attribute value for the closest surrounding include element
+     * parent, or null if this {@link CanvasViewInfo} is not rendered as part of an
+     * include tag.
+     *
+     * @return the layout url attribute value for the surrounding include tag, or null if
+     *         not applicable
+     */
+    public String getIncludeUrl() {
+        CanvasViewInfo curr = this;
+        while (curr != null) {
+            if (curr.mUiViewNode != null) {
+                Node node = curr.mUiViewNode.getXmlNode();
+                if (node != null && node.getNamespaceURI() == null
+                        && node.getNodeType() == Node.ELEMENT_NODE
+                        && LayoutDescriptors.VIEW_INCLUDE.equals(node.getNodeName())) {
+                    // Note: the layout attribute is NOT in the Android namespace
+                    Element element = (Element) node;
+                    String url = element.getAttribute(LayoutDescriptors.ATTR_LAYOUT);
+                    if (url.length() > 0) {
+                        return url;
+                    }
+                }
+            }
+            curr = curr.mParent;
+        }
+
+        return null;
+    }
 }
index 585f354..cb947d2 100644 (file)
@@ -336,7 +336,13 @@ public class GestureManager {
             // in rapid succession. In any case, we only want to let you double click the
             // first button to warp to XML:
             if (e.button == 1) {
-                mCanvas.showXml(e);
+                // Warp to the text editor and show the corresponding XML for the
+                // double-clicked widget
+                LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout();
+                CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p);
+                if (vi != null) {
+                    mCanvas.show(vi);
+                }
             }
         }
 
index 55d296d..22a1a88 100755 (executable)
@@ -64,9 +64,11 @@ import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.draw2d.geometry.Rectangle;
@@ -1605,6 +1607,93 @@ public class GraphicalEditorPart extends EditorPart
         styledText.setStyleRange(sr);
     }
 
+    /**
+     * Looks up the resource file corresponding to the given type
+     *
+     * @param type The type of resource to look up, such as {@link ResourceType#LAYOUT}
+     * @param name The name of the resource (not including ".xml")
+     * @param isFrameworkResource if true, the resource is a framework resource, otherwise
+     *            it's a project resource
+     * @return the resource file defining the named resource, or null if not found
+     */
+    public IPath findResourceFile(ResourceType type, String name, boolean isFrameworkResource) {
+        // FIXME: This code does not handle theme value resolution.
+        // There is code to handle this, but it's in layoutlib; we should
+        // expose that and use it here.
+
+        Map<String, Map<String, IResourceValue>> map;
+        map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes;
+        if (map == null) {
+            // Not yet configured
+            return null;
+        }
+
+        Map<String, IResourceValue> layoutMap = map.get(type.getName());
+        if (layoutMap != null) {
+            IResourceValue value = layoutMap.get(name);
+            if (value != null) {
+                String valueStr = value.getValue();
+                if (valueStr.startsWith("?")) { //$NON-NLS-1$
+                    // FIXME: It's a reference. We should resolve this properly.
+                    return null;
+                }
+                return new Path(valueStr);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Looks up the path to the file corresponding to the given attribute value, such as
+     * @layout/foo, which will return the foo.xml file in res/layout/. (The general format
+     * of the resource url is {@literal @[<package_name>:]<resource_type>/<resource_name>}.
+     *
+     * @param url the attribute url
+     * @return the path to the file defining this attribute, or null if not found
+     */
+    public IPath findResourceFile(String url) {
+        if (!url.startsWith("@")) { //$NON-NLS-1$
+            return null;
+        }
+        int typeEnd = url.indexOf('/', 1);
+        if (typeEnd == -1) {
+            return null;
+        }
+        int nameBegin = typeEnd + 1;
+        int typeBegin = 1;
+        int colon = url.lastIndexOf(':', typeEnd);
+        boolean isFrameworkResource = false;
+        if (colon != -1) {
+            // The URL contains a package name.
+
+            // FIXME: We should consult the package and Do The Right Thing.
+            // If the package is @android (which is by far the most common case),
+            // then maybe we can look up the corresponding file in the "data/" folder
+            // in the SDK.
+            // Otherwise, the package MAY be the same package as the current project,
+            // in which case we can just ignore it (because it will be exactly
+            // relative to the current project's folder), and otherwise we may
+            // have to look in other projects. Fortunately, this is not common.
+
+            String packageName = url.substring(typeBegin, colon);
+            if ("android".equals(packageName)) {  //$NON-NLS-1$
+                isFrameworkResource = true;
+            }
+
+            typeBegin = colon + 1;
+        }
+
+        String typeName = url.substring(typeBegin, typeEnd);
+        ResourceType type = ResourceType.getEnum(typeName);
+        if (type == null) {
+            return null;
+        }
+
+        String name = url.substring(nameBegin);
+        return findResourceFile(type, name, isFrameworkResource);
+    }
+
     /** This StyleRange represents a missing class link that the user can click */
     private static class ClassLinkStyleRange extends StyleRange {}
 
index 8a7dd7a..c0e69b1 100755 (executable)
@@ -31,11 +31,19 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
 import com.android.layoutlib.api.LayoutScene;
 import com.android.sdklib.SdkConstants;
 
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.ActionContributionItem;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.action.IContributionItem;
 import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IStatusLineManager;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.action.Separator;
 import org.eclipse.swt.SWT;
@@ -63,10 +71,14 @@ import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.actions.ActionFactory;
 import org.eclipse.ui.actions.ContributionItemFactory;
 import org.eclipse.ui.actions.TextActionHandler;
 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
+import org.eclipse.ui.ide.IDE;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
 import org.w3c.dom.Node;
@@ -671,18 +683,77 @@ class LayoutCanvas extends Canvas {
     }
 
     /**
-     * Show the XML element corresponding to the point under the mouse event
-     * (unless it's a root).
+     * Shows the given {@link CanvasViewInfo}, which can mean exposing its XML or if it's
+     * an included element, its corresponding file.
      *
-     * @param e A mouse event pointing on the screen whose underlying XML
-     *            element we want to view
+     * @param vi the {@link CanvasViewInfo} to be shown
      */
-    public void showXml(MouseEvent e) {
+    public void show(CanvasViewInfo vi) {
+        String url = vi.getIncludeUrl();
+        if (url != null) {
+            showInclude(url);
+        } else {
+            showXml(vi);
+        }
+    }
+
+    /**
+     * Shows the layout file referenced by the given url in the same project.
+     *
+     * @param url The layout attribute url of the form @layout/foo
+     */
+    private void showInclude(String url) {
+        GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor();
+        IPath filePath = graphicalEditor.findResourceFile(url);
+
+        IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+        IPath workspacePath = workspace.getLocation();
+        IEditorSite editorSite = graphicalEditor.getEditorSite();
+        if (workspacePath.isPrefixOf(filePath)) {
+            IPath relativePath = filePath.makeRelativeTo(workspacePath);
+            IResource xmlFile = workspace.findMember(relativePath);
+            try {
+                EditorUtility.openInEditor(xmlFile, true);
+                return;
+            } catch (PartInitException ex) {
+                AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$
+            }
+        } else {
+            // It's not a path in the workspace; look externally
+            // (this is probably an @android: path)
+            if (filePath.isAbsolute()) {
+                IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath);
+                // fileStore = fileStore.getChild(names[i]);
+                if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) {
+                    IWorkbenchPage page = editorSite.getWorkbenchWindow().getActivePage();
+                    try {
+                        IDE.openEditorOnFileStore(page, fileStore);
+                        return;
+                    } catch (PartInitException ex) {
+                        AdtPlugin.log(ex, "Can't open %$1s", url); //$NON-NLS-1$
+                    }
+                }
+            }
+        }
+
+        // Failed: display message to the user
+        String message = String.format("Could not find resource %1$s", url);
+        IStatusLineManager status = editorSite.getActionBars().getStatusLineManager();
+        status.setErrorMessage(message);
+        getDisplay().beep();
+    }
+
+    /**
+     * Show the XML element corresponding to the given {@link CanvasViewInfo} (unless it's
+     * a root).
+     *
+     * @param vi The clicked {@link CanvasViewInfo} whose underlying XML element we want
+     *            to view
+     */
+    private void showXml(CanvasViewInfo vi) {
         // Warp to the text editor and show the corresponding XML for the
         // double-clicked widget
-        LayoutPoint p = ControlPoint.create(this, e).toLayout();
-        CanvasViewInfo vi = mViewHierarchy.findViewInfoAt(p);
-        if (vi == null || vi.isRoot()) {
+        if (vi.isRoot()) {
             return;
         }