diff options
author | Siva Velusamy <vsiva@google.com> | 2013-01-17 18:01:27 -0800 |
---|---|---|
committer | Siva Velusamy <vsiva@google.com> | 2013-01-29 13:09:25 -0800 |
commit | 91866d67164c2b666cf2520795857e5412a7063e (patch) | |
tree | 8840356628276d06ef81f63b33baed0c07e6ff56 /hierarchyviewer2/libs/hierarchyviewerlib/src/com | |
parent | da7b4d7544af422698e152ddc24c7baa5333c498 (diff) | |
download | sdk-91866d67164c2b666cf2520795857e5412a7063e.zip sdk-91866d67164c2b666cf2520795857e5412a7063e.tar.gz sdk-91866d67164c2b666cf2520795857e5412a7063e.tar.bz2 |
Support modifying layout properties from hv
This CL adds support in the hierarchy viewer's UI for modifying
certain view properties. The editable properties are marked with
an image next to them. When edited, a command is sent to the
device indicating the view method to call and the arguments for it.
A free form text box is also added to the UI which allows for users
to explicitly specify the view method to call with parameters.
Change-Id: Iefd4239e91184fc43c1b4cccfc3896846ab19825
Diffstat (limited to 'hierarchyviewer2/libs/hierarchyviewerlib/src/com')
8 files changed, 675 insertions, 7 deletions
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(); } |