diff options
author | Raphael <raphael@google.com> | 2010-02-09 12:31:51 -0800 |
---|---|---|
committer | Raphael <raphael@google.com> | 2010-02-09 12:31:51 -0800 |
commit | 9aefc33d152479079646809e1572541aab1ced10 (patch) | |
tree | adc5047307523ef6e5387446764ba7e74a6e35b3 /eclipse | |
parent | 8d4922b07d0c4dff56e4d2fda919d7bc0418495d (diff) | |
download | sdk-9aefc33d152479079646809e1572541aab1ced10.zip sdk-9aefc33d152479079646809e1572541aab1ced10.tar.gz sdk-9aefc33d152479079646809e1572541aab1ced10.tar.bz2 |
ADT GLE2: Implement IViewRule.onChildSelected.
Revamp the IViewRule.on[Child]Selected to not return a closure.
There is no actual event at selection time.
Instead the callback is called directly to paint the canvas.
Change-Id: I2b50fbdff734b9c1a2c581ca5958bac43e5f325f
Diffstat (limited to 'eclipse')
9 files changed, 232 insertions, 93 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy index 2171cf3..ce491f5 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy +++ b/eclipse/plugins/com.android.ide.eclipse.adt/gscripts/android.view.View.groovy @@ -17,16 +17,9 @@ package com.android.adt.gscripts; import com.android.ide.eclipse.adt.editors.layout.gscripts.BaseViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IGraphics; import com.android.ide.eclipse.adt.editors.layout.gscripts.INode; -import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; import com.android.ide.eclipse.adt.editors.layout.gscripts.Rect; -import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; -import com.android.ide.eclipse.adt.editors.layout.gscripts.IGraphics; - -import java.util.Map; -import java.util.ArrayList; - -import groovy.lang.Closure; /** * An {@link IViewRule} for android.view.View and all its derived classes. @@ -39,35 +32,49 @@ public class AndroidViewViewRule extends BaseViewRule { // fallback when navigating the hierarchy. // TODO move all this to BaseViewRule - Closure onSelected(INode node) { - def drawSelection = { gc, name, currentNode, isMultipleSelection -> - Rect r = currentNode.getBounds(); + void onSelected(IGraphics gc, INode selectedNode, + String displayName, boolean isMultipleSelection) { + Rect r = selectedNode.getBounds(); - if (!r.isValid()) { - return; - } + if (!r.isValid()) { + return; + } - gc.setLineWidth(1); - gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); - gc.drawRect(r); + gc.setLineWidth(1); + gc.setLineStyle(IGraphics.LineStyle.LINE_SOLID); + gc.drawRect(r); - if (name == null || isMultipleSelection) { - return; - } + if (displayName == null || isMultipleSelection) { + return; + } - int xs = r.x + 2; - int ys = r.y - gc.getFontHeight(); - if (ys < 0) { - ys = r.y + r.h; - } - gc.drawString(name, xs, ys); - }; - - return drawSelection; + int xs = r.x + 2; + int ys = r.y - gc.getFontHeight(); + if (ys < 0) { + ys = r.y + r.h; + } + gc.drawString(displayName, xs, ys); } - Closure onChildSelected(INode parentNode, INode childNode) { - return null; + void onChildSelected(IGraphics gc, INode parentNode, INode childNode) { + Rect rp = parentNode.getBounds(); + Rect rc = childNode.getBounds(); + + if (rp.isValid() && rc.isValid()) { + gc.setLineWidth(1); + gc.setLineStyle(IGraphics.LineStyle.LINE_DOT); + + // top line + int m = rc.x + rc.w / 2; + gc.drawLine(m, rc.y, m, rp.y); + // bottom line + gc.drawLine(m, rc.y + rc.h, m, rp.y + rp.h); + // left line + m = rc.y + rc.h / 2; + gc.drawLine(rc.x, m, rp.x, m); + // right line + gc.drawLine(rc.x + rc.w, m, rp.x + rp.w, m); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java index 8148850..da52b03 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/INode.java @@ -49,6 +49,18 @@ public interface INode { */ Rect getBounds(); + + // ---- Hierarchy handling ---- + + + /** + * Returns the parent node of this node, corresponding to the parent view in the layout. + * The returned parent can be null when the node is the root element, or when the node is + * not yet or no longer attached to the hierarchy. + */ + INode getParent(); + + // ---- XML Editing --- /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java index 33ea938..e287885 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/IViewRule.java @@ -16,8 +16,6 @@ package com.android.ide.eclipse.adt.editors.layout.gscripts; -import groovy.lang.Closure; - import java.util.ArrayList; import java.util.Map; @@ -73,22 +71,32 @@ public interface IViewRule { /** * Called by the canvas when a view is being selected. - * The callback must return a closure that is used to draw the actual selection. - * <p/> - * The closure takes 4 arguments: <br/> - * - An {@link IGraphics} instance, to perform drawing operations. <br/> - * - The name to display, as returned by {@link #getDisplayName()}. <br/> - * - The selected {@link INode}. <br/> - * - A boolean set to true if more than one element is selected. * <p/> - * Context: foreground color already set to selection color, full opaque. + * Before the method is called, the canvas' Graphic Context is initialized + * with a foreground color already set to the desired selection color, fully + * opaque and with the default adequate font. * + * @param gc An {@link IGraphics} instance, to perform drawing operations. * @param selectedNode The node selected. Never null. - * @return Null or a closure that expects graphics, name, currentNode as input arguments. + * @param displayName The name to display, as returned by {@link #getDisplayName()}. + * @param isMultipleSelection A boolean set to true if more than one element is selected. */ - Closure onSelected(INode selectedNode); + void onSelected(IGraphics gc, INode selectedNode, + String displayName, boolean isMultipleSelection); - Closure onChildSelected(INode parentNode, INode childNode); + /** + * Called by the canvas when a single child view is being selected. + * <p/> + * Note that this is called only for single selections. + * <p/> + * This allows a parent to draw stuff around its children, for example to display + * layout attributes graphically. + * + * @param gc An {@link IGraphics} instance, to perform drawing operations. + * @param parentNode The parent of the node selected. Never null. + * @param childNode The child node that was selected. Never null. + */ + void onChildSelected(IGraphics gc, INode parentNode, INode childNode); // ==== XML Creation ==== diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java index 3f0754d..ad8e45b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/Rect.java @@ -40,6 +40,16 @@ public class Rect { } /** Initialize rectangle to the given values. They can be invalid. */ + public Rect(Rect r) { + set(r); + } + + /** Initialize rectangle to the given values. They can be invalid. */ + public Rect(Rectangle swtRect) { + set(swtRect); + } + + /** Initialize rectangle to the given values. They can be invalid. */ public void set(int x, int y, int w, int h) { this.x = x; this.y = y; @@ -49,10 +59,12 @@ public class Rect { /** Initialize rectangle to match the given one. */ public void set(Rect r) { - x = r.x; - y = r.y; - w = r.w; - h = r.h; + set(r.x, r.y, r.w, r.h); + } + + /** Initialize rectangle to match the given one. */ + public void set(Rectangle swtRect) { + set(swtRect.x, swtRect.y, swtRect.width, swtRect.height); } /** Returns a new instance of a rectangle with the same values. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java index 2ba8e3f..29d14b5 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasSelection.java @@ -16,33 +16,30 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; -import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; +import com.android.ide.eclipse.adt.editors.layout.gscripts.INode; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; import org.eclipse.swt.graphics.Rectangle; -import groovy.lang.Closure; - /** * Represents one selection in {@link LayoutCanvas}. */ /* package */ class CanvasSelection { - /** Current selected view info. Cannot be null. */ + /** Current selected view info. Can be null. */ private final CanvasViewInfo mCanvasViewInfo; - /** Current selection border rectangle. Cannot be null. */ + /** Current selection border rectangle. Null when mCanvasViewInfo is null . */ private final Rectangle mRect; + /** The node proxy for drawing the selection. Null when mCanvasViewInfo is null. */ + private final NodeProxy mNodeProxy; + /** The name displayed over the selection, typically the widget class name. Can be null. */ private final String mName; - /** Closure to use to paint the selection. */ - private Closure mPaintClosure; - - private NodeProxy mNodeProxy; /** * Creates a new {@link CanvasSelection} object. @@ -59,6 +56,7 @@ import groovy.lang.Closure; if (canvasViewInfo == null) { mRect = null; + mNodeProxy = null; } else { Rectangle r = canvasViewInfo.getSelectionRect(); mRect = new Rectangle( @@ -66,11 +64,10 @@ import groovy.lang.Closure; r.y + LayoutCanvas.IMAGE_MARGIN, r.width, r.height); + mNodeProxy = nodeFactory.create(canvasViewInfo); } mName = initDisplayName(canvasViewInfo, gre); - - initSelectedDisplay(canvasViewInfo, nodeFactory, gre); } /** @@ -97,19 +94,29 @@ import groovy.lang.Closure; } /** - * Calls the closure returned by - * {@link IViewRule#onSelected(com.android.ide.eclipse.adt.editors.layout.gscripts.INode)}. + * Calls IViewRule.onSelected on the selected view. * + * @param gre The rules engines. * @param gcWrapper The GC to use for drawing. * @param isMultipleSelection True if more than one view is selected. */ - /*package*/ void paint(GCWrapper gcWrapper, boolean isMultipleSelection) { - if (mPaintClosure != null) { - try { - mPaintClosure.call( - new Object[] { gcWrapper, mName, mNodeProxy, isMultipleSelection }); - } catch (Exception e) { - mNodeProxy.debugPrintf("Selection Paint Closure: %s", e.toString()); + /*package*/ void paintSelection(RulesEngine gre, GCWrapper gcWrapper, boolean isMultipleSelection) { + if (mNodeProxy != null) { + gre.callOnSelected(gcWrapper, mNodeProxy, mName, isMultipleSelection); + } + } + + /** + * Calls IViewRule.onChildSelected on the parent of the selected view, if it has one. + * + * @param gre The rules engines. + * @param gcWrapper The GC to use for drawing. + */ + public void paintParentSelection(RulesEngine gre, GCWrapper gcWrapper) { + if (mNodeProxy != null) { + INode parent = mNodeProxy.getParent(); + if (parent instanceof NodeProxy) { + gre.callOnChildSelected(gcWrapper, (NodeProxy)parent, mNodeProxy); } } } @@ -152,14 +159,4 @@ import groovy.lang.Closure; return name; } - - private void initSelectedDisplay(CanvasViewInfo canvasViewInfo, - NodeFactory nodeFactory, - RulesEngine gre) { - NodeProxy proxy = nodeFactory.create(canvasViewInfo); - Closure result = gre.callOnSelected(proxy); - - mNodeProxy = proxy; - mPaintClosure = result; - } } 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 8bb38eb..21de9c5 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 @@ -274,6 +274,8 @@ import java.util.ListIterator; mLastValidViewInfoRoot = new CanvasViewInfo(result.getRootView()); setImage(result.getImage()); + updateNodeProxies(mLastValidViewInfoRoot); + // Check if the selection is still the same (based on the object keys) // and eventually recompute their bounds. for (ListIterator<CanvasSelection> it = mSelections.listIterator(); it.hasNext(); ) { @@ -357,6 +359,33 @@ import java.util.ListIterator; //--- + + /** + * Creates or updates the node proxy for this canvas view info. + * <p/> + * Since proxies are reused, this will update the bounds of an existing proxy when the + * canvas is refreshed and a view changes position or size. + * <p/> + * This is a recursive call that updates the whole hierarchy starting at the given + * view info. + */ + private void updateNodeProxies(CanvasViewInfo vi) { + + if (vi == null) { + return; + } + + UiViewElementNode key = vi.getUiViewKey(); + + if (key != null) { + mNodeFactory.create(vi); + } + + for (CanvasViewInfo child : vi.getChildren()) { + updateNodeProxies(child); + } + } + /** * Sets the image of the last *successful* rendering. * Converts the AWT image into an SWT image. @@ -429,12 +458,22 @@ import java.util.ListIterator; gc.drawRectangle(mHoverRect); } - gc.setForeground(mSelectionFgColor); - boolean isMultipleSelection = mSelections.size() > 1; - for (CanvasSelection s : mSelections) { - s.paint(mGCWrapper, isMultipleSelection); + int n = mSelections.size(); + if (n > 0) { + boolean isMultipleSelection = n > 1; + + if (n == 1) { + gc.setForeground(mSelectionFgColor); + mSelections.get(0).paintParentSelection(mRulesEngine, mGCWrapper); + } + + for (CanvasSelection s : mSelections) { + gc.setForeground(mSelectionFgColor); + s.paintSelection(mRulesEngine, mGCWrapper, isMultipleSelection); + } } + drawDropZones(gc); } finally { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactory.java index 2b56c3a..d6251c8 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactory.java @@ -61,9 +61,14 @@ public class NodeFactory { private NodeProxy create(UiViewElementNode uiNode, Rectangle bounds) { NodeProxy proxy = mNodeMap.get(uiNode); - if (proxy == null || !proxy.getBounds().equals(bounds)) { + if (proxy == null) { + // Create a new proxy if the key doesn't exist proxy = new NodeProxy(uiNode, bounds, this); mNodeMap.put(uiNode, proxy); + + } else if (bounds != null && !proxy.getBounds().equals(bounds)) { + // Update the bounds if necessary + proxy.setBounds(bounds); } return proxy; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index d0c1568..02d2d57 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -55,19 +55,23 @@ public class NodeProxy implements INode { * Creates a new {@link INode} that wraps an {@link UiViewElementNode} that is * actually valid in the current UI/XML model. The view may not be part of the canvas * yet (e.g. if it has just been dynamically added and the canvas hasn't reloaded yet.) + * <p/> + * This method is package protected. To create a node, please use {@link NodeFactory} instead. * * @param uiNode The node to wrap. - * @param bounds The bounds of a the view in the canvas. Must be a valid rect for a view - * that is actually in the canvas and must be null (or an invalid rect) for a view - * that has just been added dynamically to the model. + * @param bounds The bounds of a the view in the canvas. Must be either: <br/> + * - a valid rect for a view that is actually in the canvas <br/> + * - <b>*or*</b> null (or an invalid rect) for a view that has just been added dynamically + * to the model. We never store a null bounds rectangle in the node, a null rectangle + * will be converted to an invalid rectangle. */ - public NodeProxy(UiViewElementNode uiNode, Rectangle bounds, NodeFactory factory) { + /*package*/ NodeProxy(UiViewElementNode uiNode, Rectangle bounds, NodeFactory factory) { mNode = uiNode; mFactory = factory; if (bounds == null) { mBounds = new Rect(); } else { - mBounds = new Rect(bounds.x, bounds.y, bounds.width, bounds.height); + mBounds = new Rect(bounds); } } @@ -82,10 +86,34 @@ public class NodeProxy implements INode { return mBounds; } + + /** + * Updates the bounds of this node proxy. Bounds cannot be null, but it can be invalid. + * This is a package-protected method, only the {@link NodeFactory} uses this method. + */ + /*package*/ void setBounds(Rectangle bounds) { + mBounds.set(bounds); + } + /* package */ UiViewElementNode getNode() { return mNode; } + + // ---- Hierarchy handling ---- + + public INode getParent() { + if (mNode != null) { + UiElementNode p = mNode.getUiParent(); + if (p instanceof UiViewElementNode) { + return mFactory.create((UiViewElementNode) p); + } + } + + return null; + } + + // ---- XML Editing --- public void editXml(String undoName, final Closure c) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index 94588b2..12c2dd7 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.editors.layout.gscripts.DropZone; +import com.android.ide.eclipse.adt.editors.layout.gscripts.IGraphics; import com.android.ide.eclipse.adt.editors.layout.gscripts.INode; import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; import com.android.ide.eclipse.adt.editors.layout.gscripts.Point; @@ -35,7 +36,6 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; -import groovy.lang.Closure; import groovy.lang.GroovyClassLoader; import java.io.InputStream; @@ -113,23 +113,54 @@ public class RulesEngine { return null; } - public Closure callOnSelected(NodeProxy selectedNode) { + /** + * Invokes {@link IViewRule#onSelected(IGraphics, INode, String, boolean)} + * on the rule matching the specified element. + * + * @param gc An {@link IGraphics} instance, to perform drawing operations. + * @param selectedNode The node selected. Never null. + * @param displayName The name to display, as returned by {@link IViewRule#getDisplayName()}. + * @param isMultipleSelection A boolean set to true if more than one element is selected. + */ + public void callOnSelected(IGraphics gc, NodeProxy selectedNode, + String displayName, boolean isMultipleSelection) { // try to find a rule for this element's FQCN IViewRule rule = loadRule(selectedNode.getNode()); if (rule != null) { try { - return rule.onSelected(selectedNode); + rule.onSelected(gc, selectedNode, displayName, isMultipleSelection); } catch (Exception e) { - logError("%s.getDisplayName() failed: %s", + logError("%s.onSelected() failed: %s", rule.getClass().getSimpleName(), e.toString()); } } + } - return null; + /** + * Invokes {@link IViewRule#onChildSelected(IGraphics, INode, INode)} + * on the rule matching the specified element. + * + * @param gc An {@link IGraphics} instance, to perform drawing operations. + * @param parentNode The parent of the node selected. Never null. + * @param childNode The child node that was selected. Never null. + */ + public void callOnChildSelected(IGraphics gc, NodeProxy parentNode, NodeProxy childNode) { + // try to find a rule for this element's FQCN + IViewRule rule = loadRule(parentNode.getNode()); + if (rule != null) { + try { + rule.onChildSelected(gc, parentNode, childNode); + + } catch (Exception e) { + logError("%s.onChildSelected() failed: %s", + rule.getClass().getSimpleName(), + e.toString()); + } + } } /** |