ADT GLE: support multiple selection and alternate selection.
Change-Id: Ia7ce5fb2365898fdcae73eacbaf6d08b091d7586
This commit is contained in:
@@ -40,6 +40,10 @@ import org.eclipse.swt.widgets.Display;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
import java.awt.image.Raster;
|
import java.awt.image.Raster;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the image rendered by the {@link GraphicalEditorPart} and handles
|
* Displays the image rendered by the {@link GraphicalEditorPart} and handles
|
||||||
@@ -51,46 +55,59 @@ import java.awt.image.Raster;
|
|||||||
* TODO list:
|
* TODO list:
|
||||||
* - gray on error, keep select but disable d'n'd.
|
* - gray on error, keep select but disable d'n'd.
|
||||||
* - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
|
* - make sure it is scrollable (Canvas derives from Scrollable, so prolly just setting bounds.)
|
||||||
* - handle selection (will need the model, aka the root node).
|
|
||||||
* - handle drop target (from palette).
|
* - handle drop target (from palette).
|
||||||
* - handle drag'n'drop (internal, for moving/duplicating).
|
* - handle drag'n'drop (internal, for moving/duplicating).
|
||||||
* - handle context menu (depending on selection).
|
* - handle context menu (depending on selection).
|
||||||
* - selection synchronization with the outline (both ways).
|
* - selection synchronization with the outline (both ways).
|
||||||
* - preserve selection during editor input change if applicable (e.g. when changing configuration.)
|
|
||||||
*/
|
*/
|
||||||
public class LayoutCanvas extends Canvas {
|
public class LayoutCanvas extends Canvas {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Margin around the rendered image. Should be enough space to display the layout
|
||||||
|
* width and height pseudo widgets.
|
||||||
|
*/
|
||||||
private static final int IMAGE_MARGIN = 5;
|
private static final int IMAGE_MARGIN = 5;
|
||||||
private static final int SELECTION_MARGIN = 2;
|
/**
|
||||||
|
* Minimal size of the selection, in case an empty view or layout is selected.
|
||||||
|
*/
|
||||||
|
private static final int SELECTION_MIN_SIZE = 6;
|
||||||
|
|
||||||
private ILayoutResult mLastValidResult;
|
private ILayoutResult mLastValidResult;
|
||||||
|
private ViewInfo mLastValidViewInfoRoot;
|
||||||
|
|
||||||
/** Current background image. Null when there's no image. */
|
/** Current background image. Null when there's no image. */
|
||||||
private Image mImage;
|
private Image mImage;
|
||||||
|
|
||||||
/** Current selected view info. Null when none is selected. */
|
private final LinkedList<Selection> mSelections = new LinkedList<Selection>();
|
||||||
private ILayoutViewInfo mSelectionViewInfo;
|
|
||||||
/** Current selection border rectangle. Null when there's no selection. */
|
|
||||||
private Rectangle mSelectionRect;
|
|
||||||
/** The name displayed over the selection, typically the widget class name. */
|
|
||||||
private String mSelectionName;
|
|
||||||
/** Selection border color. Do not dispose, it's a system color. */
|
/** Selection border color. Do not dispose, it's a system color. */
|
||||||
private Color mSelectionFgColor;
|
private Color mSelectionFgColor;
|
||||||
|
|
||||||
/** Selection name font. Do not dispose, it's a system font. */
|
/** Selection name font. Do not dispose, it's a system font. */
|
||||||
private Font mSelectionFont;
|
private Font mSelectionFont;
|
||||||
|
|
||||||
/** Pixel height of the font displaying the selection name. Initially set to 0 and only
|
/** Pixel height of the font displaying the selection name. Initially set to 0 and only
|
||||||
* initialized in onPaint() when we have a GC. */
|
* initialized in onPaint() when we have a GC. */
|
||||||
private int mSelectionFontHeight;
|
private int mSelectionFontHeight;
|
||||||
|
|
||||||
/** Current hover view info. Null when no mouse hover. */
|
/** Current hover view info. Null when no mouse hover. */
|
||||||
private ILayoutViewInfo mHoverViewInfo;
|
private ViewInfo mHoverViewInfo;
|
||||||
|
|
||||||
/** Current mouse hover border rectangle. Null when there's no mouse hover. */
|
/** Current mouse hover border rectangle. Null when there's no mouse hover. */
|
||||||
private Rectangle mHoverRect;
|
private Rectangle mHoverRect;
|
||||||
/** Hover border color. Do not dispose, it's a system color. */
|
|
||||||
|
/** Hover border color. Must be disposed, it's NOT a system color. */
|
||||||
private Color mHoverFgColor;
|
private Color mHoverFgColor;
|
||||||
|
|
||||||
private boolean mIsResultValid;
|
private AlternateSelection mAltSelection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the last {@link #setResult(ILayoutResult)} provided a valid {@link ILayoutResult}
|
||||||
|
* in which case it is also available in {@link #mLastValidResult}.
|
||||||
|
* When false this means the canvas is displaying an out-dated result image & bounds and some
|
||||||
|
* features should be disabled accordingly such a drag'n'drop.
|
||||||
|
*/
|
||||||
|
private boolean mIsResultValid;
|
||||||
|
|
||||||
|
|
||||||
public LayoutCanvas(Composite parent, int style) {
|
public LayoutCanvas(Composite parent, int style) {
|
||||||
@@ -98,9 +115,8 @@ public class LayoutCanvas extends Canvas {
|
|||||||
|
|
||||||
Display d = getDisplay();
|
Display d = getDisplay();
|
||||||
mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
|
mSelectionFgColor = d.getSystemColor(SWT.COLOR_RED);
|
||||||
mHoverFgColor = mSelectionFgColor;
|
mHoverFgColor = new Color(d, 0xFF, 0x99, 0x00); // orange
|
||||||
|
mSelectionFont = d.getSystemFont();
|
||||||
mSelectionFont = d.getSystemFont();
|
|
||||||
|
|
||||||
addPaintListener(new PaintListener() {
|
addPaintListener(new PaintListener() {
|
||||||
public void paintControl(PaintEvent e) {
|
public void paintControl(PaintEvent e) {
|
||||||
@@ -129,6 +145,11 @@ public class LayoutCanvas extends Canvas {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the result of the layout rendering. The result object indicates if the layout
|
* Sets the result of the layout rendering. The result object indicates if the layout
|
||||||
* rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
|
* rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
|
||||||
@@ -148,16 +169,29 @@ public class LayoutCanvas extends Canvas {
|
|||||||
|
|
||||||
if (mIsResultValid && result != null) {
|
if (mIsResultValid && result != null) {
|
||||||
mLastValidResult = result;
|
mLastValidResult = result;
|
||||||
|
mLastValidViewInfoRoot = new ViewInfo(result.getRootView());
|
||||||
setImage(result.getImage());
|
setImage(result.getImage());
|
||||||
|
|
||||||
// Check if the selection is still the same (based on its key)
|
// Check if the selection is still the same (based on the object keys)
|
||||||
// and eventually recompute its bounds.
|
// and eventually recompute their bounds.
|
||||||
if (mSelectionViewInfo != null) {
|
for (ListIterator<Selection> it = mSelections.listIterator(); it.hasNext(); ) {
|
||||||
ILayoutViewInfo vi = findViewInfoKey(
|
Selection s = it.next();
|
||||||
mSelectionViewInfo.getViewKey(),
|
|
||||||
result.getRootView());
|
// Check the if the selected object still exists
|
||||||
setSelection(vi);
|
Object key = s.getViewInfo().getKey();
|
||||||
|
ViewInfo vi = findViewInfoKey(key, mLastValidViewInfoRoot);
|
||||||
|
|
||||||
|
// Remove the previous selection -- if the selected object still exists
|
||||||
|
// we need to recompute its bounds in case it moved so we'll insert a new one
|
||||||
|
// at the same place.
|
||||||
|
it.remove();
|
||||||
|
if (vi != null) {
|
||||||
|
it.add(new Selection(vi));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove the current alternate selection views
|
||||||
|
mAltSelection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
@@ -184,6 +218,9 @@ public class LayoutCanvas extends Canvas {
|
|||||||
mImage = new Image(getDisplay(), imageData);
|
mImage = new Image(getDisplay(), imageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints the canvas in response to paint events.
|
||||||
|
*/
|
||||||
private void onPaint(PaintEvent e) {
|
private void onPaint(PaintEvent e) {
|
||||||
GC gc = e.gc;
|
GC gc = e.gc;
|
||||||
|
|
||||||
@@ -212,19 +249,27 @@ public class LayoutCanvas extends Canvas {
|
|||||||
mSelectionFontHeight = fm.getHeight();
|
mSelectionFontHeight = fm.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mSelectionRect != null) {
|
for (Selection s : mSelections) {
|
||||||
gc.setForeground(mSelectionFgColor);
|
drawSelection(gc, s);
|
||||||
gc.setLineStyle(SWT.LINE_SOLID);
|
}
|
||||||
gc.drawRectangle(mSelectionRect);
|
}
|
||||||
|
|
||||||
if (mSelectionName != null) {
|
private void drawSelection(GC gc, Selection s) {
|
||||||
int x = mSelectionRect.x + 2;
|
Rectangle r = s.getRect();
|
||||||
int y = mSelectionRect.y - mSelectionFontHeight;
|
|
||||||
if (y < 0) {
|
gc.setForeground(mSelectionFgColor);
|
||||||
y = mSelectionRect.y + mSelectionRect.height;
|
gc.setLineStyle(SWT.LINE_SOLID);
|
||||||
}
|
gc.drawRectangle(s.mRect);
|
||||||
gc.drawString(mSelectionName, x, y, true /*transparent*/);
|
|
||||||
|
String name = s.getName();
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
int xs = r.x + 2;
|
||||||
|
int ys = r.y - mSelectionFontHeight;
|
||||||
|
if (ys < 0) {
|
||||||
|
ys = r.y + r.height;
|
||||||
}
|
}
|
||||||
|
gc.drawString(name, xs, ys, true /*transparent*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +278,8 @@ public class LayoutCanvas extends Canvas {
|
|||||||
*/
|
*/
|
||||||
private void onMouseMove(MouseEvent e) {
|
private void onMouseMove(MouseEvent e) {
|
||||||
if (mLastValidResult != null) {
|
if (mLastValidResult != null) {
|
||||||
ILayoutViewInfo root = mLastValidResult.getRootView();
|
ViewInfo root = mLastValidViewInfoRoot;
|
||||||
ILayoutViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN, root);
|
ViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN, root);
|
||||||
|
|
||||||
// We don't hover on the root since it's not a widget per see and it is always there.
|
// We don't hover on the root since it's not a widget per see and it is always there.
|
||||||
if (vi == root) {
|
if (vi == root) {
|
||||||
@@ -244,10 +289,12 @@ public class LayoutCanvas extends Canvas {
|
|||||||
boolean needsUpdate = vi != mHoverViewInfo;
|
boolean needsUpdate = vi != mHoverViewInfo;
|
||||||
mHoverViewInfo = vi;
|
mHoverViewInfo = vi;
|
||||||
|
|
||||||
mHoverRect = vi == null ? null : getViewInfoRect(vi);
|
if (vi == null) {
|
||||||
if (mHoverRect != null) {
|
mHoverRect = null;
|
||||||
mHoverRect.x += IMAGE_MARGIN;
|
} else {
|
||||||
mHoverRect.y += IMAGE_MARGIN;
|
Rectangle r = vi.getSelectionRect();
|
||||||
|
mHoverRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN,
|
||||||
|
r.width, r.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsUpdate) {
|
if (needsUpdate) {
|
||||||
@@ -262,12 +309,125 @@ public class LayoutCanvas extends Canvas {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs selection on mouse up (not mouse down).
|
* Performs selection on mouse up (not mouse down).
|
||||||
|
* <p/>
|
||||||
|
* Shift key is used to toggle in multi-selection.
|
||||||
|
* Alt key is used to cycle selection through objects at the same level than the one
|
||||||
|
* pointed at (i.e. click on an object then alt-click to cycle).
|
||||||
*/
|
*/
|
||||||
private void onMouseUp(MouseEvent e) {
|
private void onMouseUp(MouseEvent e) {
|
||||||
if (mLastValidResult != null) {
|
if (mLastValidResult != null) {
|
||||||
ILayoutViewInfo vi = findViewInfoAt(e.x - IMAGE_MARGIN, e.y - IMAGE_MARGIN,
|
|
||||||
mLastValidResult.getRootView());
|
boolean isShift = (e.stateMask & SWT.SHIFT) != 0;
|
||||||
setSelection(vi);
|
boolean isAlt = (e.stateMask & SWT.ALT) != 0;
|
||||||
|
|
||||||
|
int x = e.x - IMAGE_MARGIN;
|
||||||
|
int y = e.y - IMAGE_MARGIN;
|
||||||
|
ViewInfo vi = findViewInfoAt(x, y, mLastValidViewInfoRoot);
|
||||||
|
|
||||||
|
if (isShift && !isAlt) {
|
||||||
|
// Case where shift is pressed: pointed object is toggled.
|
||||||
|
|
||||||
|
// reset alternate selection if any
|
||||||
|
mAltSelection = null;
|
||||||
|
|
||||||
|
// If nothing has been found at the cursor, assume it might be a user error
|
||||||
|
// and avoid clearing the existing selection.
|
||||||
|
|
||||||
|
if (vi != null) {
|
||||||
|
// toggle this selection on-off: remove it if already selected
|
||||||
|
if (deselect(vi)) {
|
||||||
|
redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise add it.
|
||||||
|
mSelections.add(new Selection(vi));
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (isAlt) {
|
||||||
|
// Case where alt is pressed: select or cycle the object pointed at.
|
||||||
|
|
||||||
|
// Note: if shift and alt are pressed, shift is ignored. The alternate selection
|
||||||
|
// mechanism does not reset the current multiple selection unless they intersect.
|
||||||
|
|
||||||
|
// We need to remember the "origin" of the alternate selection, to be
|
||||||
|
// able to continue cycling through it later. If there's no alternate selection,
|
||||||
|
// create one. If there's one but not for the same origin object, create a new
|
||||||
|
// one too.
|
||||||
|
if (mAltSelection == null || mAltSelection.getOriginatingView() != vi) {
|
||||||
|
mAltSelection = new AlternateSelection(vi, findAltViewInfoAt(
|
||||||
|
x, y, mLastValidViewInfoRoot, null));
|
||||||
|
|
||||||
|
// deselect them all, in case they were partially selected
|
||||||
|
deselectAll(mAltSelection.getAltViews());
|
||||||
|
|
||||||
|
// select the current one
|
||||||
|
ViewInfo vi2 = mAltSelection.getCurrent();
|
||||||
|
if (vi2 != null) {
|
||||||
|
mSelections.addFirst(new Selection(vi2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We're trying to cycle through the current alternate selection.
|
||||||
|
// First remove the current object.
|
||||||
|
ViewInfo vi2 = mAltSelection.getCurrent();
|
||||||
|
deselect(vi2);
|
||||||
|
|
||||||
|
// Now select the next one.
|
||||||
|
vi2 = mAltSelection.getNext();
|
||||||
|
if (vi2 != null) {
|
||||||
|
mSelections.addFirst(new Selection(vi2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Case where no modifier is pressed: either select or reset the selection.
|
||||||
|
|
||||||
|
// reset alternate selection if any
|
||||||
|
mAltSelection = null;
|
||||||
|
|
||||||
|
// reset (multi)selection if any
|
||||||
|
if (mSelections.size() > 0) {
|
||||||
|
if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) {
|
||||||
|
// Selection remains the same, don't touch it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSelections.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vi != null) {
|
||||||
|
mSelections.add(new Selection(vi));
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deselects a view info. Returns true if the object was actually selected. */
|
||||||
|
private boolean deselect(ViewInfo viewInfo) {
|
||||||
|
if (viewInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ListIterator<Selection> it = mSelections.listIterator(); it.hasNext(); ) {
|
||||||
|
Selection s = it.next();
|
||||||
|
if (viewInfo == s.getViewInfo()) {
|
||||||
|
it.remove();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deselects multiple view infos, */
|
||||||
|
private void deselectAll(List<ViewInfo> viewInfos) {
|
||||||
|
for (ListIterator<Selection> it = mSelections.listIterator(); it.hasNext(); ) {
|
||||||
|
Selection s = it.next();
|
||||||
|
if (viewInfos.contains(s.getViewInfo())) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,61 +435,20 @@ public class LayoutCanvas extends Canvas {
|
|||||||
// pass, not used yet.
|
// pass, not used yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSelection(ILayoutViewInfo viewInfo) {
|
|
||||||
boolean needsUpdate = viewInfo != mSelectionViewInfo;
|
|
||||||
mSelectionViewInfo = viewInfo;
|
|
||||||
|
|
||||||
mSelectionRect = viewInfo == null ? null : getViewInfoRect(viewInfo);
|
|
||||||
if (mSelectionRect != null) {
|
|
||||||
mSelectionRect.x += IMAGE_MARGIN;
|
|
||||||
mSelectionRect.y += IMAGE_MARGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = viewInfo == null ? null : viewInfo.getName();
|
|
||||||
if (name != null) {
|
|
||||||
// The name is typically a fully-qualified class name. Let's make it a tad shorter.
|
|
||||||
|
|
||||||
if (name.startsWith("android.")) { // $NON-NLS-1$
|
|
||||||
// For android classes, convert android.foo.Name to android...Name
|
|
||||||
int first = name.indexOf('.');
|
|
||||||
int last = name.lastIndexOf('.');
|
|
||||||
if (last > first) {
|
|
||||||
name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// For custom non-android classes, it's best to keep the 2 first segments of
|
|
||||||
// the namespace, e.g. we want to get something like com.example...MyClass
|
|
||||||
int first = name.indexOf('.');
|
|
||||||
first = name.indexOf('.', first + 1);
|
|
||||||
int last = name.lastIndexOf('.');
|
|
||||||
if (last > first) {
|
|
||||||
name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mSelectionName = name;
|
|
||||||
|
|
||||||
if (needsUpdate) {
|
|
||||||
redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to find a child with the same view key in the view info sub-tree.
|
* Tries to find a child with the same view key in the view info sub-tree.
|
||||||
* Returns null if not found.
|
* Returns null if not found.
|
||||||
*/
|
*/
|
||||||
private ILayoutViewInfo findViewInfoKey(Object viewKey, ILayoutViewInfo viewInfo) {
|
private ViewInfo findViewInfoKey(Object viewKey, ViewInfo viewInfo) {
|
||||||
if (viewInfo.getViewKey() == viewKey) {
|
if (viewInfo.getKey() == viewKey) {
|
||||||
return viewInfo;
|
return viewInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to find a matching child
|
// try to find a matching child
|
||||||
if (viewInfo.getChildren() != null) {
|
for (ViewInfo child : viewInfo.getChildren()) {
|
||||||
for (ILayoutViewInfo child : viewInfo.getChildren()) {
|
ViewInfo v = findViewInfoKey(viewKey, child);
|
||||||
ILayoutViewInfo v = findViewInfoKey(viewKey, child);
|
if (v != null) {
|
||||||
if (v != null) {
|
return v;
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,17 +461,15 @@ public class LayoutCanvas extends Canvas {
|
|||||||
*
|
*
|
||||||
* Returns null if not found.
|
* Returns null if not found.
|
||||||
*/
|
*/
|
||||||
private ILayoutViewInfo findViewInfoAt(int x, int y, ILayoutViewInfo viewInfo) {
|
private ViewInfo findViewInfoAt(int x, int y, ViewInfo viewInfo) {
|
||||||
Rectangle r = getViewInfoRect(viewInfo);
|
Rectangle r = viewInfo.getSelectionRect();
|
||||||
if (r.contains(x, y)) {
|
if (r.contains(x, y)) {
|
||||||
|
|
||||||
// try to find a matching child first
|
// try to find a matching child first
|
||||||
if (viewInfo.getChildren() != null) {
|
for (ViewInfo child : viewInfo.getChildren()) {
|
||||||
for (ILayoutViewInfo child : viewInfo.getChildren()) {
|
ViewInfo v = findViewInfoAt(x, y, child);
|
||||||
ILayoutViewInfo v = findViewInfoAt(x, y, child);
|
if (v != null) {
|
||||||
if (v != null) {
|
return v;
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,26 +480,295 @@ public class LayoutCanvas extends Canvas {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private ArrayList<ViewInfo> findAltViewInfoAt(
|
||||||
* Returns the bounds of the view info as a rectangle.
|
int x, int y, ViewInfo parent, ArrayList<ViewInfo> outList) {
|
||||||
* In case the view has a null width or null height, it is expanded using
|
Rectangle r;
|
||||||
* {@link #SELECTION_MARGIN}.
|
|
||||||
*/
|
|
||||||
private Rectangle getViewInfoRect(ILayoutViewInfo viewInfo) {
|
|
||||||
int x = viewInfo.getLeft();
|
|
||||||
int y = viewInfo.getTop();
|
|
||||||
int w = viewInfo.getRight() - x;
|
|
||||||
int h = viewInfo.getBottom() - y;
|
|
||||||
|
|
||||||
if (w == 0) {
|
if (outList == null) {
|
||||||
x -= SELECTION_MARGIN;
|
outList = new ArrayList<ViewInfo>();
|
||||||
w += 2 * SELECTION_MARGIN;
|
|
||||||
}
|
// add the parent root only once
|
||||||
if (h == 0) {
|
r = parent.getSelectionRect();
|
||||||
y -= SELECTION_MARGIN;
|
if (r.contains(x, y)) {
|
||||||
h += 2* SELECTION_MARGIN;
|
outList.add(parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Rectangle(x, y, w, h);
|
if (parent.getChildren().size() > 0) {
|
||||||
|
// then add all children that match the position
|
||||||
|
for (ViewInfo child : parent.getChildren()) {
|
||||||
|
r = child.getSelectionRect();
|
||||||
|
if (r.contains(x, y)) {
|
||||||
|
outList.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally recurse in the children
|
||||||
|
for (ViewInfo child : parent.getChildren()) {
|
||||||
|
r = child.getSelectionRect();
|
||||||
|
if (r.contains(x, y)) {
|
||||||
|
findAltViewInfoAt(x, y, child, outList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a {@link ILayoutViewInfo} in a structure more adapted to our needs.
|
||||||
|
* The only large difference is that we keep both the original bounds of the view info
|
||||||
|
* and we pre-compute the selection bounds which are absolute to the rendered image (where
|
||||||
|
* as the original bounds are relative to the parent view.)
|
||||||
|
* <p/>
|
||||||
|
* Each view also know its parent, which should be handy later.
|
||||||
|
* <p/>
|
||||||
|
* We can't alter {@link ILayoutViewInfo} as it is part of the LayoutBridge and needs to
|
||||||
|
* have a fixed API.
|
||||||
|
*/
|
||||||
|
private static class ViewInfo {
|
||||||
|
private final Rectangle mRealRect;
|
||||||
|
private final Rectangle mSelectionRect;
|
||||||
|
private final String mName;
|
||||||
|
private final Object mKey;
|
||||||
|
private final ViewInfo mParent;
|
||||||
|
private final ArrayList<ViewInfo> mChildren = new ArrayList<ViewInfo>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link ViewInfo} hierarchy based on a given {@link ILayoutViewInfo}
|
||||||
|
* hierarchy. This call is recursives and builds a full tree.
|
||||||
|
*
|
||||||
|
* @param viewInfo The root of the {@link ILayoutViewInfo} hierarchy.
|
||||||
|
*/
|
||||||
|
public ViewInfo(ILayoutViewInfo viewInfo) {
|
||||||
|
this(viewInfo, null /*parent*/, 0 /*parentX*/, 0 /*parentY*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ViewInfo(ILayoutViewInfo viewInfo, ViewInfo parent, int parentX, int parentY) {
|
||||||
|
mParent = parent;
|
||||||
|
mKey = viewInfo.getViewKey();
|
||||||
|
mName = viewInfo.getName();
|
||||||
|
|
||||||
|
int x = viewInfo.getLeft();
|
||||||
|
int y = viewInfo.getTop();
|
||||||
|
int w = viewInfo.getRight() - x;
|
||||||
|
int h = viewInfo.getBottom() - y;
|
||||||
|
|
||||||
|
mRealRect = new Rectangle(x, y, w, h);
|
||||||
|
|
||||||
|
if (parent != null) {
|
||||||
|
x += parentX;
|
||||||
|
y += parentY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewInfo.getChildren() != null) {
|
||||||
|
for (ILayoutViewInfo child : viewInfo.getChildren()) {
|
||||||
|
mChildren.add(new ViewInfo(child, this, x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust selection bounds for views which are too small to select
|
||||||
|
|
||||||
|
if (w < SELECTION_MIN_SIZE) {
|
||||||
|
int d = (SELECTION_MIN_SIZE - w) / 2;
|
||||||
|
x -= d;
|
||||||
|
w += SELECTION_MIN_SIZE - w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h < SELECTION_MIN_SIZE) {
|
||||||
|
int d = (SELECTION_MIN_SIZE - h) / 2;
|
||||||
|
y -= d;
|
||||||
|
h += SELECTION_MIN_SIZE - h;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSelectionRect = new Rectangle(x, y, w - 1, h - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the original {@link ILayoutResult} bounds, relative to the parent. */
|
||||||
|
public Rectangle getRealRect() {
|
||||||
|
return mRealRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the absolute selection bounds of the view info as a rectangle.
|
||||||
|
* The selection bounds will always have a size greater or equal to
|
||||||
|
* {@link #SELECTION_MIN_SIZE}.
|
||||||
|
* The width/height is inclusive (i.e. width = right-left-1).
|
||||||
|
* This is in absolute "screen" coordinates (relative to the rendered bitmap).
|
||||||
|
*/
|
||||||
|
public Rectangle getSelectionRect() {
|
||||||
|
return mSelectionRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the view key. Could be null, although unlikely.
|
||||||
|
* @see ILayoutViewInfo#getViewKey()
|
||||||
|
*/
|
||||||
|
public Object getKey() {
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parent {@link ViewInfo}.
|
||||||
|
* It is null for the root and non-null for children.
|
||||||
|
*/
|
||||||
|
public ViewInfo getParent() {
|
||||||
|
return mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of children of this {@link ViewInfo}.
|
||||||
|
* The list is never null. It can be empty.
|
||||||
|
* By contract, this.getChildren().get(0..n-1).getParent() == this.
|
||||||
|
*/
|
||||||
|
public ArrayList<ViewInfo> getChildren() {
|
||||||
|
return mChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the {@link ViewInfo}.
|
||||||
|
* Could be null, although unlikely.
|
||||||
|
* Experience shows this is the full qualified Java name of the View.
|
||||||
|
* @see ILayoutViewInfo#getName()
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents one selection.
|
||||||
|
*/
|
||||||
|
private static class Selection {
|
||||||
|
/** Current selected view info. Cannot be null. */
|
||||||
|
private final ViewInfo mViewInfo;
|
||||||
|
|
||||||
|
/** Current selection border rectangle. Cannot be null. */
|
||||||
|
private final Rectangle mRect;
|
||||||
|
|
||||||
|
/** The name displayed over the selection, typically the widget class name. Can be null. */
|
||||||
|
private final String mName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Selection} object.
|
||||||
|
* @param viewInfo The view info being selected. Must not be null.
|
||||||
|
*/
|
||||||
|
public Selection(ViewInfo viewInfo) {
|
||||||
|
|
||||||
|
assert viewInfo != null;
|
||||||
|
|
||||||
|
mViewInfo = viewInfo;
|
||||||
|
|
||||||
|
if (viewInfo == null) {
|
||||||
|
mRect = null;
|
||||||
|
} else {
|
||||||
|
Rectangle r = viewInfo.getSelectionRect();
|
||||||
|
mRect = new Rectangle(r.x + IMAGE_MARGIN, r.y + IMAGE_MARGIN, r.width, r.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = viewInfo == null ? null : viewInfo.getName();
|
||||||
|
if (name != null) {
|
||||||
|
// The name is typically a fully-qualified class name. Let's make it a tad shorter.
|
||||||
|
|
||||||
|
if (name.startsWith("android.")) { // $NON-NLS-1$
|
||||||
|
// For android classes, convert android.foo.Name to android...Name
|
||||||
|
int first = name.indexOf('.');
|
||||||
|
int last = name.lastIndexOf('.');
|
||||||
|
if (last > first) {
|
||||||
|
name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For custom non-android classes, it's best to keep the 2 first segments of
|
||||||
|
// the namespace, e.g. we want to get something like com.example...MyClass
|
||||||
|
int first = name.indexOf('.');
|
||||||
|
first = name.indexOf('.', first + 1);
|
||||||
|
int last = name.lastIndexOf('.');
|
||||||
|
if (last > first) {
|
||||||
|
name = name.substring(0, first) + ".." + name.substring(last); // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected view info. Cannot be null.
|
||||||
|
*/
|
||||||
|
public ViewInfo getViewInfo() {
|
||||||
|
return mViewInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selection border rectangle.
|
||||||
|
* Cannot be null.
|
||||||
|
*/
|
||||||
|
public Rectangle getRect() {
|
||||||
|
return mRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name displayed over the selection, typically the widget class name.
|
||||||
|
* Can be null.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information for the current alternate selection, i.e. the possible selected items
|
||||||
|
* that are located at the same x/y as the original view, either sibling or parents.
|
||||||
|
*/
|
||||||
|
private static class AlternateSelection {
|
||||||
|
private final ViewInfo mOriginatingView;
|
||||||
|
private final List<ViewInfo> mAltViews;
|
||||||
|
private int mIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new alternate selection based on the given originating view and the
|
||||||
|
* given list of alternate views. Both cannot be null.
|
||||||
|
*/
|
||||||
|
public AlternateSelection(ViewInfo originatingView, List<ViewInfo> altViews) {
|
||||||
|
assert originatingView != null;
|
||||||
|
assert altViews != null;
|
||||||
|
mOriginatingView = originatingView;
|
||||||
|
mAltViews = altViews;
|
||||||
|
mIndex = altViews.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the list of alternate views. Cannot be null. */
|
||||||
|
public List<ViewInfo> getAltViews() {
|
||||||
|
return mAltViews;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the originating view. Cannot be null. */
|
||||||
|
public ViewInfo getOriginatingView() {
|
||||||
|
return mOriginatingView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current alternate view to select.
|
||||||
|
* Initially this is the top-most view.
|
||||||
|
*/
|
||||||
|
public ViewInfo getCurrent() {
|
||||||
|
return mIndex >= 0 ? mAltViews.get(mIndex) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the current view to be the next one and then returns it.
|
||||||
|
* This loops through the alternate views.
|
||||||
|
*/
|
||||||
|
public ViewInfo getNext() {
|
||||||
|
if (mIndex == 0) {
|
||||||
|
mIndex = mAltViews.size() - 1;
|
||||||
|
} else if (mIndex > 0) {
|
||||||
|
mIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user