package org.droiddraw.widget; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; import java.util.Vector; import org.droiddraw.property.BooleanProperty; import org.droiddraw.property.Property; import org.droiddraw.property.StringProperty; public class RelativeLayout extends AbstractLayout { Hashtable<Widget, Vector<Relation>> relations; boolean repositioning = false; public static final int PADDING = 4; public static final String[] propNames = new String[] { "android:layout_toRightOf", "android:layout_toLeftOf", "android:layout_above", "android:layout_below", "android:layout_alignRight", "android:layout_alignLeft", "android:layout_alignTop", "android:layout_alignBottom", "android:layout_alignParentRight", "android:layout_alignParentLeft", "android:layout_alignParentTop", "android:layout_alignParentBottom", "android:layout_centerHorizontal", "android:layout_centerVertical", "android:layout_centerInParent", "android:layout_alignBaseline" }; public static final RelationType[] rts = { RelationType.TO_RIGHT, RelationType.TO_LEFT, RelationType.ABOVE, RelationType.BELOW, RelationType.RIGHT, RelationType.LEFT, RelationType.TOP, RelationType.BOTTOM, RelationType.PARENT_RIGHT, RelationType.PARENT_LEFT, RelationType.PARENT_TOP, RelationType.PARENT_BOTTOM, RelationType.CENTER_HORIZONTAL, RelationType.CENTER_VERTICAL, RelationType.CENTER, RelationType.BASELINE }; public RelativeLayout() { super("RelativeLayout"); relations = new Hashtable<Widget, Vector<Relation>>(); } protected boolean isRelatedTo(Widget w, Widget to) { Vector<Relation> rels = relations.get(w); if (rels == null) { return false; } if (w.equals(to)) { return true; } else { boolean r1 = false; boolean r2 = false; if (rels.size() > 0) r1 = isRelatedTo(rels.get(0).getRelatedTo(), to); if (rels.size() > 1) r2 = isRelatedTo(rels.get(1).getRelatedTo(), to); return r1 || r2; } } protected static final String strip(String id) { int ix = id.indexOf("@"); int ix2 = id.indexOf("+", ix); if (ix2 > ix) { return id.substring(ix2+1); } return id.substring(ix+1); } public Widget findById(String id) { for (Widget w : widgets) { if (strip(w.getId()).equals(strip(id))) { return w; } } return null; } public void applyRelation(RelationType r, Widget w, Widget parent) { int x = w.getX(); int y = w.getY(); if (r == RelationType.ABOVE) { y = parent.getY()-PADDING-w.getHeight(); } if (r == RelationType.BELOW) { y = parent.getY()+parent.getHeight()+PADDING; } if (r == RelationType.BOTTOM) { y = parent.getY()+parent.getHeight()-w.getHeight(); } if (r == RelationType.TOP) { y = parent.getY(); } if (r == RelationType.PARENT_TOP) { y = 0; } if (r == RelationType.PARENT_BOTTOM) { y = parent.getHeight()-w.getHeight(); } if (r == RelationType.CENTER || r == RelationType.CENTER_VERTICAL) { y = parent.getHeight()/2-w.getHeight()/2; } if (r == RelationType.BASELINE) { y = parent.getY()+parent.getBaseline()-w.getBaseline(); } if (r == RelationType.LEFT) { x = parent.getX(); } if (r == RelationType.RIGHT) { x = parent.getX()+parent.getWidth()-w.getWidth(); } if (r == RelationType.TO_LEFT) { x = parent.getX()+w.getX()-w.getWidth(); } if (r == RelationType.TO_RIGHT) { x = parent.getX()+parent.getWidth()+PADDING+w.getMargin(Widget.LEFT); } if (r == RelationType.PARENT_LEFT) { x = 0; } if (r == RelationType.PARENT_RIGHT) { x = parent.getWidth()-w.getWidth(); } if (r == RelationType.CENTER || r == RelationType.CENTER_HORIZONTAL) { x = parent.getWidth()/2-w.getWidth()/2; } w.setPosition(x,y); } protected int closestVertical(Widget w, Widget wd, int[] dist) { int y = wd.getY(); if (wd == w.getParent()) { y = 0; } dist[0] = Math.abs(w.getY()-y); dist[1] = Math.abs(w.getY()+w.getHeight()-(y+wd.getHeight())); dist[2] = Math.abs(w.getY()+w.getHeight()-y); dist[3] = Math.abs(w.getY()-(y+wd.getHeight())); dist[4] = Math.abs(w.getY()+w.getBaseline()-(y+wd.getBaseline())); int min = dist[0]; int mode = 0; for (int i=1;i<dist.length;i++) { if (dist[i] < min) { min = dist[i]; mode = i; } if (wd == w.getParent() && i == 1) break; } // Test for centering if (wd == w.getParent() && Math.abs(w.getY()+(w.getHeight())/2 - (wd.getHeight()/2)) < min) { min = Math.abs((w.getY()+w.getHeight())/2 - (wd.getHeight()/2)); mode = dist.length; } dist[0] = min; return mode; } protected int closestHorizontal(Widget w, Widget wd, int[] dist) { int x = wd.getX(); if (wd == w.getParent()) { x = 0; } dist[0] = Math.abs(w.getX()-x); dist[1] = Math.abs(w.getX()+w.getWidth()-(x+wd.getWidth())); dist[2] = Math.abs(w.getX()+w.getWidth()-x); dist[3] = Math.abs(w.getX()-(x+wd.getWidth())); dist[4] = Integer.MAX_VALUE; int min = dist[0]; int mode = 0; for (int i=1;i<dist.length;i++) { if (dist[i] < min) { min = dist[i]; mode = i; } if (wd == w.getParent() && i == 1) break; } // Test for centering if (wd == w.getParent() && Math.abs(w.getX()+w.getWidth()/2 - (wd.getWidth()/2)) < min) { min = Math.abs((w.getX()+w.getWidth())/2 - (wd.getWidth()/2)); mode = dist.length; } dist[0] = min; return mode; } @Override public void positionWidget(Widget w) { Vector<Relation> v = relations.get(w); if (v == null) { v = new Vector<Relation>(); relations.put(w, v); } v.clear(); boolean positioned = false; for (int i=0;i<propNames.length;i++) { Property p = w.getPropertyByAttName(propNames[i]); if (p != null) { if (p instanceof StringProperty) { String id = ((StringProperty)p).getStringValue(); Widget parent = findById(id); if (parent == null) { parent = this; } v.add(new Relation(w, parent, rts[i])); applyRelation(rts[i], w, parent); w.removeProperty(p); positioned = true; } } } if (positioned) return; /*if (widgets.size() == 1) { w.setPosition(0,0); return; }*/ Widget closestTop = null; Widget closestLeft = null; int closeVert = Integer.MAX_VALUE; int closeHorz = Integer.MAX_VALUE; int modeVert=0; int modeHorz=0; int dist[] = new int[5]; if (w.getParent() != null) { modeVert = closestVertical(w, w.getParent(), dist); if (modeVert < 2 || modeVert == dist.length) { closeVert = dist[0]; closestTop = w.getParent(); } else { modeVert = 0; } modeHorz = closestHorizontal(w, w.getParent(), dist); if (modeHorz < 2 || modeHorz == dist.length) { closeHorz = dist[0]; closestLeft = w.getParent(); } else { modeHorz = 0; } } for (Widget wd : widgets) { if (isRelatedTo(wd, w) || wd == w) continue; int mode = closestVertical(w, wd, dist); if (dist[0] < closeVert) { closeVert = dist[0]; closestTop = wd; modeVert = mode; } mode = closestHorizontal(w, wd, dist); if (dist[0] < closeHorz) { closeHorz = dist[0]; closestLeft = wd; modeHorz = mode; } } if (closestTop == null || closestLeft == null) { w.setPosition(0,0); return; } int x, y; x = w.getX(); y = w.getY(); if (closestTop == w.getParent()) { switch (modeVert) { case 0: y = 0; v.add(new Relation(w, closestTop, RelationType.PARENT_TOP)); break; case 1: y = closestTop.getHeight()-w.getHeight(); v.add(new Relation(w, closestTop, RelationType.PARENT_BOTTOM)); break; case 5: y = closestTop.getHeight()/2-w.getHeight()/2; v.add(new Relation(w, closestTop, RelationType.CENTER_VERTICAL)); break; } } else { switch (modeVert) { case 0: y = closestTop.getY(); v.add(new Relation(w, closestTop, RelationType.TOP)); break; case 1: y = closestTop.getY()+closestTop.getHeight()-w.getHeight(); v.add(new Relation(w, closestTop, RelationType.BOTTOM)); break; case 2: y = closestTop.getY()-w.getHeight()-PADDING; v.add(new Relation(w, closestTop, RelationType.ABOVE)); break; case 3: y = closestTop.getY()+closestTop.getHeight()+PADDING; v.add(new Relation(w, closestTop, RelationType.BELOW)); break; case 4: y = closestTop.getY()+closestTop.getBaseline()-w.getBaseline(); v.add(new Relation(w, closestTop, RelationType.BASELINE)); break; } } if (closestLeft == w.getParent()) { switch (modeHorz) { case 0: x = 0; v.add(new Relation(w, closestLeft, RelationType.PARENT_LEFT)); break; case 1: x = closestLeft.getWidth()-w.getWidth(); v.add(new Relation(w, closestLeft, RelationType.PARENT_RIGHT)); break; case 5: x = closestLeft.getWidth()/2-w.getWidth()/2; v.add(new Relation(w, closestLeft, RelationType.CENTER_HORIZONTAL)); break; } } else { switch (modeHorz) { case 0: x = closestLeft.getX(); v.add(new Relation(w, closestLeft, RelationType.LEFT)); break; case 1: x = closestLeft.getX()+closestLeft.getWidth()-w.getWidth(); v.add(new Relation(w, closestLeft, RelationType.RIGHT)); break; case 2: x = closestLeft.getX()-w.getWidth()-PADDING; v.add(new Relation(w, closestLeft, RelationType.TO_LEFT)); break; case 3: x = closestLeft.getX()+closestLeft.getWidth()+PADDING; v.add(new Relation(w, closestLeft, RelationType.TO_RIGHT)); break; } } w.setPosition(x, y); Collections.sort(widgets, new Comparator<Widget>() { public int compare(Widget w1, Widget w2) { if (isRelatedTo(w1, w2)) { return -1; } else if (isRelatedTo(w2, w1)) { return 1; } else return 0; } }); } public Vector<Widget> getParents(Widget w) { Vector<Relation> rels = relations.get(w); Vector<Widget> parents = new Vector<Widget>(); if (rels != null && rels.size() > 0) { for (Relation r : rels) { parents.add(r.getRelatedTo()); } } return parents; } public Set<Widget> getRoots(Widget w) { Vector<Widget> parents = getParents(w); Set<Widget> roots = new HashSet<Widget>(); for (Widget parent : parents) { Set<Widget> parentRoots = getRoots(parent); for (Widget root : parentRoots) { roots.add(root); } } return roots; } @SuppressWarnings("unchecked") @Override public synchronized void repositionAllWidgets() { if (!repositioning) { repositioning = true; Vector<Widget> ws = (Vector<Widget>)widgets.clone(); widgets.clear(); for (Widget w : ws) { addWidget(w); } repositioning = false; } } public void addEditableProperties(Widget w) {} public void removeEditableProperties(Widget w) {} public void addOutputProperties(Widget w, Vector<Property> properties) { Vector<Relation> rels = relations.get(w); if (rels != null) { for (Relation r : rels) { if (r.getRelation().equals(RelationType.LEFT)) { properties.add(new StringProperty("relation", "android:layout_alignLeft", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.TO_LEFT)) { properties.add(new StringProperty("relation", "android:layout_toLeftOf", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.RIGHT)) { properties.add(new StringProperty("relation", "android:layout_alignRight", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.TO_RIGHT)) { properties.add(new StringProperty("relation", "android:layout_toRightOf", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.ABOVE)) { properties.add(new StringProperty("relation", "android:layout_above", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.BELOW)) { properties.add(new StringProperty("relation", "android:layout_below", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.TOP)) { properties.add(new StringProperty("relation", "android:layout_alignTop", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.BOTTOM)) { properties.add(new StringProperty("relation", "android:layout_alignBottom", r.getRelatedTo().getId(), false)); } else if (r.getRelation().equals(RelationType.CENTER_VERTICAL)) { properties.add(new BooleanProperty("relation", "android:layout_centerVertical", true, false)); } else if (r.getRelation().equals(RelationType.CENTER_HORIZONTAL)) { properties.add(new BooleanProperty("relation", "android:layout_centerHorizontal", true, false)); } else if (r.getRelation().equals(RelationType.PARENT_BOTTOM)) { properties.add(new BooleanProperty("relation", "android:layout_alignParentBottom", true, false)); } else if (r.getRelation().equals(RelationType.PARENT_TOP)) { properties.add(new BooleanProperty("relation", "android:layout_alignParentTop", true, false)); } else if (r.getRelation().equals(RelationType.PARENT_LEFT)) { properties.add(new BooleanProperty("relation", "android:layout_alignParentLeft", true, false)); } else if (r.getRelation().equals(RelationType.PARENT_RIGHT)) { properties.add(new BooleanProperty("relation", "android:layout_alignParentRight", true, false)); } else if (r.getRelation().equals(RelationType.BASELINE)) { properties.add(new StringProperty("relation", "android:layout_alignBaseline", r.getRelatedTo().getId(), false)); } } } } public static enum RelationType {TOP, ABOVE, BOTTOM, BELOW, LEFT, TO_LEFT, RIGHT, TO_RIGHT, BASELINE, CENTER_VERTICAL, CENTER_HORIZONTAL, CENTER, PARENT_TOP, PARENT_BOTTOM, PARENT_RIGHT, PARENT_LEFT} public static class Relation { RelationType relation; Widget parent; Widget widget; public Relation(Widget widget, Widget parent, RelationType relation) { this.widget = widget; this.parent = parent; this.relation = relation; } public RelationType getRelation() { return relation; } public Widget getRelatedTo() { return parent; } public Widget getWidget() { return widget; } } }