From 99fd7eee15c89fd45b884842c44371326f851930 Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Sun, 21 Nov 2010 21:37:20 -0800 Subject: [PATCH] Open included layout on double click On double click, if the clicked area originates from an '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 | 4 + .../android/ide/common/layout/TableLayoutRule.java | 2 +- .../adt/internal/editors/layout/LayoutEditor.java | 9 +++ .../layout/descriptors/LayoutDescriptors.java | 9 ++- .../editors/layout/gle2/CanvasViewInfo.java | 31 ++++++++ .../editors/layout/gle2/GestureManager.java | 8 +- .../editors/layout/gle2/GraphicalEditorPart.java | 89 ++++++++++++++++++++++ .../internal/editors/layout/gle2/LayoutCanvas.java | 87 +++++++++++++++++++-- 8 files changed, 228 insertions(+), 11 deletions(-) diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index b9356c00c..20ab93cde 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -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 diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java index a4d528caf..a5d772c22 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java @@ -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 } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index 7dc916991..74e86a066 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -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 diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java index 202153314..903e030b7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java @@ -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 + *

+ * NOTE: 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 diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java index 303c58df0..cc2c4895b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java @@ -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; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java index 585f354e9..cb947d29d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java @@ -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); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 55d296df0..22a1a88e3 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -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> map; + map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes; + if (map == null) { + // Not yet configured + return null; + } + + Map 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 @[:]/}. + * + * @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 {} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 8a7dd7a84..c0e69b132 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -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; } -- 2.11.0