diff options
Diffstat (limited to 'hierarchyviewer2/libs/hierarchyviewerlib/src')
10 files changed, 678 insertions, 8 deletions
diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk index 1afbc92..59753e1 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk @@ -20,8 +20,10 @@ LOCAL_JAVA_RESOURCE_DIRS := ../src LOCAL_JAR_MANIFEST := ../manifest.txt -LOCAL_JAVA_LIBRARIES := ddmlib \ +LOCAL_JAVA_LIBRARIES := common \ + ddmlib \ ddmuilib \ + guava-tools \ swt \ org.eclipse.jface_3.6.2.M20110210-1200 \ org.eclipse.core.commands_3.6.0.I20100512-1500 diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java index 0f0cf65..cba35f2 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java @@ -48,6 +48,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; @@ -615,6 +616,20 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener, } } + public void invokeMethodOnSelectedView(final String method, final List<Object> args) { + final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection(); + if (selectedNode != null) { + executeInBackground("Invoke View Method", new Runnable() { + @Override + public void run() { + IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice()); + hvDevice.invokeViewMethod(selectedNode.viewNode.window, selectedNode.viewNode, + method, args); + } + }); + } + } + public void loadAllViews() { executeInBackground("Loading all views", new Runnable() { @Override diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DdmViewDebugDevice.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DdmViewDebugDevice.java index 0a6e938..0172995 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DdmViewDebugDevice.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DdmViewDebugDevice.java @@ -144,7 +144,7 @@ public class DdmViewDebugDevice extends AbstractHvDevice implements IDeviceChang try { HandleViewDebug.listViewRoots(c, handler); } catch (IOException e) { - Log.e(TAG, e); + Log.i(TAG, "No connection to client: " + cd.getClientDescription()); continue; } @@ -215,7 +215,11 @@ public class DdmViewDebugDevice extends AbstractHvDevice implements IDeviceChang return null; } - byte[] data = handler.getData(10, TimeUnit.SECONDS); + byte[] data = handler.getData(20, TimeUnit.SECONDS); + if (data == null) { + return null; + } + String viewHierarchy = new String(data, Charset.forName("UTF-8")); return DeviceBridge.parseViewHierarchy(new BufferedReader(new StringReader(viewHierarchy)), window); @@ -370,4 +374,44 @@ public class DdmViewDebugDevice extends AbstractHvDevice implements IDeviceChang reloadWindows(); } } + + @Override + public boolean isViewUpdateEnabled() { + return true; + } + + @Override + public void invokeViewMethod(Window window, ViewNode viewNode, String method, + List<?> args) { + Client c = window.getClient(); + if (c == null) { + return; + } + + String viewRoot = window.getTitle(); + try { + HandleViewDebug.invokeMethod(c, viewRoot, viewNode.toString(), method, args.toArray()); + } catch (IOException e) { + Log.e(TAG, e); + } + } + + @Override + public boolean setLayoutParameter(Window window, ViewNode viewNode, String property, + int value) { + Client c = window.getClient(); + if (c == null) { + return false; + } + + String viewRoot = window.getTitle(); + try { + HandleViewDebug.setLayoutParameter(c, viewRoot, viewNode.toString(), property, value); + } catch (IOException e) { + Log.e(TAG, e); + return false; + } + + return true; + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/HvDeviceFactory.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/HvDeviceFactory.java index c38a9cc..efc7926 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/HvDeviceFactory.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/HvDeviceFactory.java @@ -21,13 +21,26 @@ import com.android.ddmlib.ClientData; import com.android.ddmlib.IDevice; public class HvDeviceFactory { - private static final boolean ALWAYS_USE_VIEWSERVER = false; // for debugging + private static final String sHvProtoEnvVar = + System.getenv("android.hvproto"); //$NON-NLS-1$ public static IHvDevice create(IDevice device) { - if (ALWAYS_USE_VIEWSERVER) { + // default to old mechanism until the new one is fully tested + if (sHvProtoEnvVar == null || + !"ddm".equalsIgnoreCase(sHvProtoEnvVar)) { //$NON-NLS-1$ return new ViewServerDevice(device); } + // Wait for a few seconds after the device has been connected to + // allow all the clients to be initialized. Specifically, we need to wait + // until the client data is filled with the list of features supported + // by the client. + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // ignore + } + boolean ddmViewHierarchy = false; // see if any of the clients on the device support view hierarchy via DDMS diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/IHvDevice.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/IHvDevice.java index fe8d1ba..6f1fd37 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/IHvDevice.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/IHvDevice.java @@ -24,6 +24,8 @@ import com.android.hierarchyviewerlib.ui.util.PsdFile; import org.eclipse.swt.graphics.Image; +import java.util.List; + /** Represents a device that can perform view debug operations. */ public interface IHvDevice { /** @@ -51,6 +53,10 @@ public interface IHvDevice { void requestLayout(ViewNode viewNode); void outputDisplayList(ViewNode viewNode); + boolean isViewUpdateEnabled(); + void invokeViewMethod(Window window, ViewNode viewNode, String method, List<?> args); + boolean setLayoutParameter(Window window, ViewNode viewNode, String property, int value); + void addWindowChangeListener(IWindowChangeListener l); void removeWindowChangeListener(IWindowChangeListener l); } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewServerDevice.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewServerDevice.java index 0febcef..4445e9a 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewServerDevice.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/ViewServerDevice.java @@ -26,6 +26,8 @@ import com.android.hierarchyviewerlib.ui.util.PsdFile; import org.eclipse.swt.graphics.Image; +import java.util.List; + public class ViewServerDevice extends AbstractHvDevice { static final String TAG = "ViewServerDevice"; @@ -146,4 +148,22 @@ public class ViewServerDevice extends AbstractHvDevice { WindowUpdater.stopListenForWindowChanges(l, mDevice); } } + + @Override + public boolean isViewUpdateEnabled() { + return false; + } + + @Override + public void invokeViewMethod(Window window, ViewNode viewNode, String method, + List<?> args) { + // not supported + } + + @Override + public boolean setLayoutParameter(Window window, ViewNode viewNode, String property, + int value) { + // not supported + return false; + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DevicePropertyEditingSupport.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DevicePropertyEditingSupport.java new file mode 100644 index 0000000..1bbc97f --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/DevicePropertyEditingSupport.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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.android.hierarchyviewerlib.ui; + +import com.android.SdkConstants; +import com.android.hierarchyviewerlib.device.IHvDevice; +import com.android.hierarchyviewerlib.models.ViewNode; +import com.android.hierarchyviewerlib.models.ViewNode.Property; +import com.android.utils.SdkUtils; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; + +import java.text.ParseException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public class DevicePropertyEditingSupport { + public enum PropertyType { + INTEGER, + INTEGER_OR_CONSTANT, + ENUM, + }; + + private static final List<IDevicePropertyEditor> sDevicePropertyEditors = Arrays.asList( + new LayoutPropertyEditor(), + new PaddingPropertyEditor() + ); + + public boolean canEdit(Property p) { + return getPropertyEditorFor(p) != null; + } + + private IDevicePropertyEditor getPropertyEditorFor(Property p) { + for (IDevicePropertyEditor pe: sDevicePropertyEditors) { + if (pe.canEdit(p)) { + return pe; + } + } + + return null; + } + + public PropertyType getPropertyType(Property p) { + return getPropertyEditorFor(p).getType(p); + } + + public String[] getPropertyRange(Property p) { + return getPropertyEditorFor(p).getPropertyRange(p); + } + + public boolean setValue(Collection<Property> properties, Property p, Object newValue, + ViewNode viewNode, IHvDevice device) { + return getPropertyEditorFor(p).setValue(properties, p, newValue, viewNode, device); + } + + private static String stripCategoryPrefix(String name) { + return name.substring(name.indexOf(':') + 1); + } + + private interface IDevicePropertyEditor { + boolean canEdit(Property p); + PropertyType getType(Property p); + String[] getPropertyRange(Property p); + boolean setValue(Collection<Property> properties, Property p, Object newValue, + ViewNode viewNode, IHvDevice device); + } + + private static class LayoutPropertyEditor implements IDevicePropertyEditor { + private static final Set<String> sLayoutPropertiesWithStringValues = + ImmutableSet.of(SdkConstants.ATTR_LAYOUT_WIDTH, + SdkConstants.ATTR_LAYOUT_HEIGHT, + SdkConstants.ATTR_LAYOUT_GRAVITY); + + private static final int MATCH_PARENT = -1; + private static final int FILL_PARENT = -1; + private static final int WRAP_CONTENT = -2; + + private enum LayoutGravity { + top(0x30), + bottom(0x50), + left(0x03), + right(0x05), + center_vertical(0x10), + fill_vertical(0x70), + center_horizontal(0x01), + fill_horizontal(0x07), + center(0x11), + fill(0x77), + clip_vertical(0x80), + clip_horizontal(0x08), + start(0x00800003), + end(0x00800005); + + private final int mValue; + + private LayoutGravity(int v) { + mValue = v; + } + } + + /** + * Returns true if this is a layout property with either a known string value, or an + * integer value. + */ + @Override + public boolean canEdit(Property p) { + String name = stripCategoryPrefix(p.name); + if (!name.startsWith(SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX)) { + return false; + } + + if (sLayoutPropertiesWithStringValues.contains(name)) { + return true; + } + + try { + SdkUtils.parseLocalizedInt(p.value); + return true; + } catch (ParseException e) { + return false; + } + } + + @Override + public PropertyType getType(Property p) { + String name = stripCategoryPrefix(p.name); + if (sLayoutPropertiesWithStringValues.contains(name)) { + return PropertyType.INTEGER_OR_CONSTANT; + } else { + return PropertyType.INTEGER; + } + } + + @Override + public String[] getPropertyRange(Property p) { + return new String[0]; + } + + @Override + public boolean setValue(Collection<Property> properties, Property p, Object newValue, + ViewNode viewNode, IHvDevice device) { + String name = stripCategoryPrefix(p.name); + + // nothing to do if same as current value + if (p.value.equals(newValue)) { + return false; + } + + int value = -1; + String textValue = null; + + if (SdkConstants.ATTR_LAYOUT_GRAVITY.equals(name)) { + value = 0; + StringBuilder sb = new StringBuilder(20); + for (String attr: Splitter.on('|').split((String) newValue)) { + LayoutGravity g; + try { + g = LayoutGravity.valueOf(attr); + } catch (IllegalArgumentException e) { + // ignore this gravity attribute + continue; + } + + value |= g.mValue; + + if (sb.length() > 0) { + sb.append('|'); + } + sb.append(g.name()); + } + textValue = sb.toString(); + } else if (SdkConstants.ATTR_LAYOUT_HEIGHT.equals(name) + || SdkConstants.ATTR_LAYOUT_WIDTH.equals(name)) { + // newValue is of type string, but its contents may be a named constant or a integer + String s = (String) newValue; + if (s.equalsIgnoreCase(SdkConstants.VALUE_MATCH_PARENT)) { + textValue = SdkConstants.VALUE_MATCH_PARENT; + value = MATCH_PARENT; + } else if (s.equalsIgnoreCase(SdkConstants.VALUE_FILL_PARENT)) { + textValue = SdkConstants.VALUE_FILL_PARENT; + value = FILL_PARENT; + } else if (s.equalsIgnoreCase(SdkConstants.VALUE_WRAP_CONTENT)) { + textValue = SdkConstants.VALUE_WRAP_CONTENT; + value = WRAP_CONTENT; + } + } + + if (textValue == null) { + try { + value = Integer.parseInt((String) newValue); + } catch (NumberFormatException e) { + return false; + } + } + + // attempt to set the value on the device + name = name.substring(SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX.length()); + if (device.setLayoutParameter(viewNode.window, viewNode, name, value)) { + p.value = textValue != null ? textValue : (String) newValue; + } + + return true; + } + } + + private static class PaddingPropertyEditor implements IDevicePropertyEditor { + // These names should match the field names used for padding in the Framework's View class + private static final String PADDING_LEFT = "mPaddingLeft"; //$NON-NLS-1$ + private static final String PADDING_RIGHT = "mPaddingRight"; //$NON-NLS-1$ + private static final String PADDING_TOP = "mPaddingTop"; //$NON-NLS-1$ + private static final String PADDING_BOTTOM = "mPaddingBottom"; //$NON-NLS-1$ + + private static final Set<String> sPaddingProperties = ImmutableSet.of( + PADDING_LEFT, PADDING_RIGHT, PADDING_TOP, PADDING_BOTTOM); + + @Override + public boolean canEdit(Property p) { + return sPaddingProperties.contains(stripCategoryPrefix(p.name)); + } + + @Override + public PropertyType getType(Property p) { + return PropertyType.INTEGER; + } + + @Override + public String[] getPropertyRange(Property p) { + return new String[0]; + } + + /** + * Set padding: Since the only view method is setPadding(l, t, r, b), we need access + * to all 4 padding's to update any particular one. + */ + @Override + public boolean setValue(Collection<Property> properties, Property prop, Object newValue, + ViewNode viewNode, IHvDevice device) { + int v; + try { + v = Integer.parseInt((String) newValue); + } catch (NumberFormatException e) { + return false; + } + + int pLeft = 0; + int pRight = 0; + int pTop = 0; + int pBottom = 0; + + String propName = stripCategoryPrefix(prop.name); + for (Property p: properties) { + String name = stripCategoryPrefix(p.name); + if (!sPaddingProperties.contains(name)) { + continue; + } + + if (name.equals(PADDING_LEFT)) { + pLeft = propName.equals(PADDING_LEFT) ? + v : SdkUtils.parseLocalizedInt(p.value, 0); + } else if (name.equals(PADDING_RIGHT)) { + pRight = propName.equals(PADDING_RIGHT) ? + v : SdkUtils.parseLocalizedInt(p.value, 0); + } else if (name.equals(PADDING_TOP)) { + pTop = propName.equals(PADDING_TOP) ? + v : SdkUtils.parseLocalizedInt(p.value, 0); + } else if (name.equals(PADDING_BOTTOM)) { + pBottom = propName.equals(PADDING_BOTTOM) ? + v : SdkUtils.parseLocalizedInt(p.value, 0); + } + } + + // invoke setPadding() on the device + device.invokeViewMethod(viewNode.window, viewNode, "setPadding", Arrays.asList( + Integer.valueOf(pLeft), + Integer.valueOf(pTop), + Integer.valueOf(pRight), + Integer.valueOf(pBottom) + )); + + // update the value set in the property (to avoid reading all properties back from + // the device) + prop.value = Integer.toString(v); + return true; + } + } +} diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/InvokeMethodPrompt.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/InvokeMethodPrompt.java new file mode 100644 index 0000000..944a57a --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/InvokeMethodPrompt.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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.android.hierarchyviewerlib.ui; + +import com.android.ddmlib.Log; +import com.android.hierarchyviewerlib.HierarchyViewerDirector; +import com.android.hierarchyviewerlib.device.IHvDevice; +import com.android.hierarchyviewerlib.models.TreeViewModel; +import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener; +import com.android.hierarchyviewerlib.models.ViewNode; +import com.android.hierarchyviewerlib.ui.util.DrawableViewNode; +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class InvokeMethodPrompt extends Composite implements ITreeChangeListener { + private TreeViewModel mModel; + private DrawableViewNode mSelectedNode; + private Text mText; + private static final Splitter CMD_SPLITTER = Splitter.on(CharMatcher.anyOf(", ")) + .trimResults().omitEmptyStrings(); + + public InvokeMethodPrompt(Composite parent) { + super(parent, SWT.NONE); + setLayout(new FillLayout()); + + mText = new Text(this, SWT.BORDER); + mText.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent ke) { + } + + @Override + public void keyPressed(KeyEvent ke) { + onKeyPress(ke); + } + }); + + mModel = TreeViewModel.getModel(); + mModel.addTreeChangeListener(this); + } + + private void onKeyPress(KeyEvent ke) { + if (ke.keyCode == SWT.CR) { + String cmd = mText.getText().trim(); + if (!cmd.isEmpty()) { + invokeViewMethod(cmd); + } + mText.setText(""); + } + } + + private void invokeViewMethod(String cmd) { + Iterator<String> segmentIterator = CMD_SPLITTER.split(cmd).iterator(); + + String method = null; + if (segmentIterator.hasNext()) { + method = segmentIterator.next(); + } else { + return; + } + + List<Object> args = new ArrayList<Object>(10); + while (segmentIterator.hasNext()) { + String arg = segmentIterator.next(); + + // check for boolean + if (arg.equalsIgnoreCase("true")) { + args.add(Boolean.TRUE); + continue; + } else if (arg.equalsIgnoreCase("false")) { + args.add(Boolean.FALSE); + continue; + } + + // see if last character gives a clue regarding the argument type + char typeSpecifier = Character.toUpperCase(arg.charAt(arg.length() - 1)); + try { + switch (typeSpecifier) { + case 'L': + args.add(Long.valueOf(arg.substring(0, arg.length()))); + break; + case 'D': + args.add(Double.valueOf(arg.substring(0, arg.length()))); + break; + case 'F': + args.add(Float.valueOf(arg.substring(0, arg.length()))); + break; + case 'S': + args.add(Short.valueOf(arg.substring(0, arg.length()))); + break; + case 'B': + args.add(Byte.valueOf(arg.substring(0, arg.length()))); + break; + default: // default to integer + args.add(Integer.valueOf(arg)); + break; + } + } catch (NumberFormatException e) { + Log.e("hv", "Unable to parse method argument: " + arg); + return; + } + } + + HierarchyViewerDirector.getDirector().invokeMethodOnSelectedView(method, args); + } + + @Override + public void selectionChanged() { + mSelectedNode = mModel.getSelection(); + refresh(); + } + + private boolean isViewUpdateEnabled(ViewNode viewNode) { + IHvDevice device = viewNode.window.getHvDevice(); + return device != null && device.isViewUpdateEnabled(); + } + + private void refresh() { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + mText.setEnabled(mSelectedNode != null + && isViewUpdateEnabled(mSelectedNode.viewNode)); + } + }); + } + + @Override + public void treeChanged() { + selectionChanged(); + } + + @Override + public void viewportChanged() { + } + + @Override + public void zoomChanged() { + } +} diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java index 90d2405..9456a0a 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/ui/PropertyViewer.java @@ -16,17 +16,27 @@ package com.android.hierarchyviewerlib.ui; +import com.android.ddmuilib.ImageLoader; +import com.android.hierarchyviewerlib.HierarchyViewerDirector; +import com.android.hierarchyviewerlib.device.IHvDevice; import com.android.hierarchyviewerlib.models.TreeViewModel; -import com.android.hierarchyviewerlib.models.ViewNode; import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener; +import com.android.hierarchyviewerlib.models.ViewNode; import com.android.hierarchyviewerlib.models.ViewNode.Property; +import com.android.hierarchyviewerlib.ui.DevicePropertyEditingSupport.PropertyType; import com.android.hierarchyviewerlib.ui.util.DrawableViewNode; import com.android.hierarchyviewerlib.ui.util.TreeColumnResizer; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; @@ -42,13 +52,17 @@ import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import java.util.ArrayList; +import java.util.Collection; public class PropertyViewer extends Composite implements ITreeChangeListener { private TreeViewModel mModel; private TreeViewer mTreeViewer; - private Tree mTree; + private TreeViewerColumn mValueColumn; + private PropertyValueEditingSupport mPropertyValueEditingSupport; + + private Image mImage; private DrawableViewNode mSelectedNode; @@ -144,6 +158,13 @@ public class PropertyViewer extends Composite implements ITreeChangeListener { @Override public Image getColumnImage(Object element, int column) { + if (mSelectedNode == null) { + return null; + } + if (column == 1 && mPropertyValueEditingSupport.canEdit(element)) { + return mImage; + } + return null; } @@ -188,6 +209,79 @@ public class PropertyViewer extends Composite implements ITreeChangeListener { } } + private class PropertyValueEditingSupport extends EditingSupport { + private DevicePropertyEditingSupport mDevicePropertyEditingSupport = + new DevicePropertyEditingSupport(); + + public PropertyValueEditingSupport(ColumnViewer viewer) { + super(viewer); + } + + @Override + protected boolean canEdit(Object element) { + if (mSelectedNode == null) { + return false; + } + + return element instanceof Property + && mSelectedNode.viewNode.window.getHvDevice().isViewUpdateEnabled() + && mDevicePropertyEditingSupport.canEdit((Property) element); + } + + @Override + protected CellEditor getCellEditor(Object element) { + Property p = (Property) element; + PropertyType type = mDevicePropertyEditingSupport.getPropertyType(p); + Composite parent = (Composite) getViewer().getControl(); + + switch (type) { + case INTEGER: + case INTEGER_OR_CONSTANT: + return new TextCellEditor(parent); + case ENUM: + String[] items = mDevicePropertyEditingSupport.getPropertyRange(p); + return new ComboBoxCellEditor(parent, items, SWT.READ_ONLY); + } + + return null; + } + + @Override + protected Object getValue(Object element) { + Property p = (Property) element; + PropertyType type = mDevicePropertyEditingSupport.getPropertyType(p); + + if (type == PropertyType.ENUM) { + // for enums, return the index of the current value in the list of possible values + String[] items = mDevicePropertyEditingSupport.getPropertyRange(p); + return Integer.valueOf(indexOf(p.value, items)); + } + + return ((Property) element).value; + } + + private int indexOf(String item, String[] items) { + for (int i = 0; i < items.length; i++) { + if (items[i].equals(item)) { + return i; + } + } + + return -1; + } + + @Override + protected void setValue(Object element, Object newValue) { + Property p = (Property) element; + IHvDevice device = mSelectedNode.viewNode.window.getHvDevice(); + Collection<Property> properties = mSelectedNode.viewNode.namedProperties.values(); + if (mDevicePropertyEditingSupport.setValue(properties, p, newValue, + mSelectedNode.viewNode, device)) { + doRefresh(); + } + } + } + public PropertyViewer(Composite parent) { super(parent, SWT.NONE); setLayout(new FillLayout()); @@ -202,6 +296,10 @@ public class PropertyViewer extends Composite implements ITreeChangeListener { TreeColumn valueColumn = new TreeColumn(mTree, SWT.NONE); valueColumn.setText("Value"); + mValueColumn = new TreeViewerColumn(mTreeViewer, valueColumn); + mPropertyValueEditingSupport = new PropertyValueEditingSupport(mTreeViewer); + mValueColumn.setEditingSupport(mPropertyValueEditingSupport); + mModel = TreeViewModel.getModel(); ContentProvider contentProvider = new ContentProvider(); mTreeViewer.setContentProvider(contentProvider); @@ -211,10 +309,14 @@ public class PropertyViewer extends Composite implements ITreeChangeListener { addDisposeListener(mDisposeListener); - new TreeColumnResizer(this, propertyColumn, valueColumn); + @SuppressWarnings("unused") + TreeColumnResizer resizer = new TreeColumnResizer(this, propertyColumn, valueColumn); addControlListener(mControlListener); + ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class); + mImage = imageLoader.loadImage("picker.png", Display.getDefault()); //$NON-NLS-1$ + treeChanged(); } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/images/picker.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/picker.png Binary files differnew file mode 100644 index 0000000..8ea2bed --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/picker.png |