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;
}
}
}