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;
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<String, Map<String, ResourceValue>> map;
+ map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes;
+ if (map == null) {
+ // Not yet configured
+ return null;
+ }
+
+ Map<String, ResourceValue> 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 {}
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;
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;
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;
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;
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.
*/
* 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) {
}
/**
- * 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;
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);
}
}
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;
+ }
}
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;
* @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.
}
}
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;
}
/**
}
// 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;
}
// 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<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();
// We need to ignore hidden attributes.