From 1f34a9b0c5cdd41dad23719898d5f71f3ee57394 Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Tue, 4 Jan 2011 18:28:43 -0800 Subject: [PATCH] Improve the Outline contents 1. Make the outline use StyledStrings such that we can use different colors for different elements in the outline. Use the decorations color for the element type that follows the id. 2. For elements that define a "text" property, include the text (or a prefix of it if it is long) in the outline. Thus, for a Button you might see something like "Button01 - "Submit Order"). 3. For elements that define a "src" property, show the source. Therefore, for an ImageView you might see "ImageView - logo". 4. For elements, show the name of the included layout. Change-Id: Ibd4c8339ea0e03c969ccaec1a67bc64436ed67af --- eclipse/dictionary.txt | 2 + .../android/ide/common/layout/LayoutConstants.java | 12 ++ .../editors/layout/gle2/GraphicalEditorPart.java | 41 +++++++ .../internal/editors/layout/gle2/OutlinePage.java | 134 +++++++++++++++++---- .../resources/uimodel/UiItemElementNode.java | 4 +- .../internal/editors/uimodel/UiElementNode.java | 50 +++++++- .../extractstring/ExtractStringRefactoring.java | 4 +- .../wizards/newproject/NewProjectWizard.java | 3 +- 8 files changed, 217 insertions(+), 33 deletions(-) diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index 5352587a8..8ab29e624 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -27,6 +27,7 @@ bytecode callback callbacks carlo +cf changeset checkbox classloader @@ -178,6 +179,7 @@ submenu supertype syncs temp +textfields thematically themed tmp diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index d9eb8fbe7..dd0af5445 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -135,4 +135,16 @@ public class LayoutConstants { /** The prefix for existing id attribute values, @id/ */ public static String ID_PREFIX = "@id/"; //$NON-NLS-1$ + + /** Prefix for resources that reference layouts */ + public static String LAYOUT_PREFIX = "@layout/"; //$NON-NLS-1$ + + /** Prefix for resources that reference drawables */ + public static String DRAWABLE_PREFIX = "@drawable/"; //$NON-NLS-1$ + + /** Prefix for resources that reference strings */ + public static String STRING_PREFIX = "@string/"; //$NON-NLS-1$ + + /** Prefix for resources that reference Android strings */ + public static String ANDROID_STRING_PREFIX = "@android:string/"; //$NON-NLS-1$ } 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 9ba31e11b..97fc96a17 100644 --- 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 @@ -16,7 +16,9 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_STRING_PREFIX; import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW; +import static com.android.ide.common.layout.LayoutConstants.STRING_PREFIX; import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG; import static com.android.sdklib.resources.Density.DEFAULT_DENSITY; @@ -1927,6 +1929,45 @@ public class GraphicalEditorPart extends EditorPart return findResourceFile(type, name, isFrameworkResource); } + /** + * Resolve the given @string reference into a literal String using the current project + * configuration + * + * @param text the text resource reference to resolve + * @return the resolved string, or null + */ + public String findString(String text) { + if (text.startsWith(STRING_PREFIX)) { + return findString(text.substring(STRING_PREFIX.length()), false); + } else if (text.startsWith(ANDROID_STRING_PREFIX)) { + return findString(text.substring(ANDROID_STRING_PREFIX.length()), true); + } else { + return text; + } + } + + private String findString(String name, boolean isFrameworkResource) { + Map> map; + map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes; + if (map == null) { + // Not yet configured + return null; + } + + Map layoutMap = map.get(ResourceType.STRING.getName()); + if (layoutMap != null) { + ResourceValue value = layoutMap.get(name); + if (value != null) { + // 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. + return value.getValue(); + } + } + + return null; + } + /** 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/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java index 0b6c9697d..4870894fc 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java @@ -16,6 +16,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; +import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; + import com.android.ide.common.api.INode; import com.android.ide.common.api.InsertType; import com.android.ide.common.layout.BaseLayoutRule; @@ -24,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; @@ -43,15 +51,16 @@ import org.eclipse.jface.action.Separator; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IElementComparer; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; @@ -73,6 +82,8 @@ import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.views.contentoutline.ContentOutlinePage; +import org.w3c.dom.Element; +import org.w3c.dom.Node; import java.util.ArrayList; import java.util.HashSet; @@ -94,6 +105,12 @@ import java.util.Set; public class OutlinePage extends ContentOutlinePage implements ISelectionListener, INullSelectionListener { + /** Label which separates outline text from additional attributes like text prefix or url */ + private static final String LABEL_SEPARATOR = " - "; + + /** Max character count in labels, used for truncation */ + private static final int LABEL_MAX_WIDTH = 50; + /** * The graphical editor that created this outline. */ @@ -435,10 +452,12 @@ public class OutlinePage extends ContentOutlinePage * Label provider for the Outline model. * Objects are going to be {@link CanvasViewInfo}. */ - private class LabelProvider implements ILabelProvider { - + private class LabelProvider extends StyledCellLabelProvider { /** * Returns the element's logo with a fallback on the android logo. + * + * @param element the tree element + * @return the image to be used as a logo */ public Image getImage(Object element) { if (element instanceof CanvasViewInfo) { @@ -464,9 +483,13 @@ public class OutlinePage extends ContentOutlinePage } /** - * Uses UiElementNode.shortDescription for the label for this tree item. + * Uses {@link UiElementNode#getStyledDescription} for the label for this tree item. */ - public String getText(Object element) { + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + StyledString styledString = null; + CanvasViewInfo vi = null; if (element instanceof CanvasViewInfo) { vi = (CanvasViewInfo) element; @@ -475,33 +498,73 @@ public class OutlinePage extends ContentOutlinePage if (element instanceof UiElementNode) { UiElementNode node = (UiElementNode) element; - return node.getShortDescription(); + styledString = node.getStyledDescription(); + Node xmlNode = node.getXmlNode(); + if (xmlNode instanceof Element) { + Element e = (Element) xmlNode; + if (e.hasAttributeNS(ANDROID_URI, ATTR_TEXT)) { + // Show the text attribute + String text = e.getAttributeNS(ANDROID_URI, ATTR_TEXT); + if (text != null && text.length() > 0 + && !text.contains(node.getDescriptor().getUiName())) { + if (text.charAt(0) == '@') { + String resolved = mGraphicalEditorPart.findString(text); + if (resolved != null) { + text = resolved; + } + } + styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); + styledString.append('"', QUALIFIER_STYLER); + styledString.append(truncate(text, styledString), QUALIFIER_STYLER); + styledString.append('"', QUALIFIER_STYLER); + } + } else if (e.hasAttributeNS(ANDROID_URI, ATTR_SRC)) { + // Show ImageView source attributes etc + String src = e.getAttributeNS(ANDROID_URI, ATTR_SRC); + if (src != null && src.length() > 0) { + if (src.startsWith(DRAWABLE_PREFIX)) { + src = src.substring(DRAWABLE_PREFIX.length()); + } + styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); + styledString.append(truncate(src, styledString), QUALIFIER_STYLER); + } + } else if (e.getTagName().equals(LayoutDescriptors.VIEW_INCLUDE)) { + // Show the include reference. + + // Note: the layout attribute is NOT in the Android namespace + String src = e.getAttribute(LayoutDescriptors.ATTR_LAYOUT); + if (src != null && src.length() > 0) { + if (src.startsWith(LAYOUT_PREFIX)) { + src = src.substring(LAYOUT_PREFIX.length()); + } + styledString.append(LABEL_SEPARATOR, QUALIFIER_STYLER); + styledString.append(truncate(src, styledString), QUALIFIER_STYLER); + } + } + } } else if (element == null && vi != null) { - // It's an inclusion-context + // It's an inclusion-context: display it Reference includedWithin = mGraphicalEditorPart.getIncludedWithin(); if (includedWithin != null) { - return includedWithin.getDisplayName(); + styledString = new StyledString(); + styledString.append(includedWithin.getDisplayName(), QUALIFIER_STYLER); } } - return element == null ? "(null)" : element.toString(); //$NON-NLS-1$ - } - - public void addListener(ILabelProviderListener listener) { - // pass - } + if (styledString == null) { + styledString = new StyledString(); + styledString.append(element == null ? "(null)" : element.toString()); + } - public void dispose() { - // pass - } + cell.setText(styledString.toString()); + cell.setStyleRanges(styledString.getStyleRanges()); + cell.setImage(getImage(element)); + super.update(cell); + } + @Override public boolean isLabelProperty(Object element, String property) { - // pass - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // pass + return super.isLabelProperty(element, property); } } @@ -846,4 +909,27 @@ public class OutlinePage extends ContentOutlinePage return null; } + + /** + * Truncates the given text such that it will fit into the given {@link StyledString} + * up to a maximum length of {@link #LABEL_MAX_WIDTH}. + * + * @param text the text to truncate + * @param string the existing string to be appended to + * @return the truncated string + */ + private static String truncate(String text, StyledString string) { + int existingLength = string.length(); + + if (text.length() + existingLength > LABEL_MAX_WIDTH) { + int truncatedLength = LABEL_MAX_WIDTH - existingLength - 3; + if (truncatedLength > 0) { + return String.format("%1$s...", text.substring(0, truncatedLength)); + } else { + return ""; //$NON-NLS-1$ + } + } + + return text; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/uimodel/UiItemElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/uimodel/UiItemElementNode.java index 1bb0f588d..539881a4a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/uimodel/UiItemElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/uimodel/UiItemElementNode.java @@ -25,14 +25,14 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; /** - * {@link UiItemElementNode} is apecial version of {@link UiElementNode} that + * {@link UiItemElementNode} is a special version of {@link UiElementNode} that * customizes the element display to include the item type attribute if present. */ public class UiItemElementNode extends UiElementNode { /** * Creates a new {@link UiElementNode} described by a given {@link ItemElementDescriptor}. - * + * * @param elementDescriptor The {@link ItemElementDescriptor} for the XML node. Cannot be null. */ public UiItemElementNode(ItemElementDescriptor elementDescriptor) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index 1150bd0d8..c55431065 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -44,6 +44,7 @@ import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.sdklib.SdkConstants; import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.viewers.StyledString; import org.eclipse.ui.views.properties.IPropertyDescriptor; import org.eclipse.ui.views.properties.IPropertySource; import org.w3c.dom.Attr; @@ -214,8 +215,17 @@ public class UiElementNode implements IPropertySource { * @return A short string describing the UI node suitable for tree views. */ public String getShortDescription() { - if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) { + String attr = getDescAttribute(); + if (attr != null) { + return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName()); + } + return mDescriptor.getUiName(); + } + + /** Returns the key attribute that can be used to describe this node, or null */ + private String getDescAttribute() { + if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) { // Application and Manifest nodes have a special treatment: they are unique nodes // so we don't bother trying to differentiate their strings and we fall back to // just using the UI name below. @@ -254,11 +264,41 @@ public class UiElementNode implements IPropertySource { } } if (attr != null && attr.length() > 0) { - return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName()); + return attr; } } - return String.format("%1$s", mDescriptor.getUiName()); + return null; + } + + /** + * Computes a styled string describing the UI node suitable for tree views. + * Similar to {@link #getShortDescription()} but styles the Strings. + * + * @return A styled string describing the UI node suitable for tree views. + */ + public StyledString getStyledDescription() { + String uiName = mDescriptor.getUiName(); + + StyledString styledString = new StyledString(); + String attr = getDescAttribute(); + if (attr != null) { + // Don't append the two when it's a repeat, e.g. Button01 (Button), + // only when the ui name is not part of the attribute + if (attr.indexOf(uiName) == -1) { + styledString.append(attr); + styledString.append(String.format(" (%1$s)", uiName), + StyledString.DECORATIONS_STYLER); + } else { + styledString.append(attr); + } + } + + if (styledString.length() == 0) { + styledString.append(uiName); + } + + return styledString; } /** @@ -880,7 +920,7 @@ public class UiElementNode implements IPropertySource { } // If we get here and parentXmlNode is null, the node is to be created - // as the root node of the document (which can't be null, cf check above). + // as the root node of the document (which can't be null, cf. check above). if (parentXmlNode == null) { parentXmlNode = doc; } @@ -1308,7 +1348,7 @@ public class UiElementNode implements IPropertySource { // Clone the current list of unknown attributes. We'll then remove from this list when // we still attributes which are still unknown. What will be left are the old unknown // attributes that have been deleted in the current XML attribute list. - @SuppressWarnings("unchecked") //$NON-NLS-1$ + @SuppressWarnings("unchecked") HashSet deleted = (HashSet) mUnknownUiAttributes.clone(); // We need to ignore hidden attributes. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java index c816259e1..f1d6aa0fc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java @@ -16,6 +16,8 @@ package com.android.ide.eclipse.adt.internal.refactorings.extractstring; +import static com.android.ide.common.layout.LayoutConstants.STRING_PREFIX; + import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -1556,7 +1558,7 @@ public class ExtractStringRefactoring extends Refactoring { xmlChange = new TextFileChange(getName(), file); xmlChange.setTextType("xml"); //$NON-NLS-1$ - String quotedReplacement = quotedAttrValue("@string/" + xmlStringId); //$NON-NLS-1$ + String quotedReplacement = quotedAttrValue(STRING_PREFIX + xmlStringId); // Prepare the change set for (IStructuredDocumentRegion regions : sdoc.getStructuredDocumentRegions()) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java index 62582681a..9cb746d1b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject; +import com.android.ide.common.layout.LayoutConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.project.AndroidNature; @@ -176,7 +177,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$ - private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$ + private static final String STRING_RSRC_PREFIX = LayoutConstants.STRING_PREFIX; private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$ private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$ -- 2.11.0