package org.geogebra.common.euclidian.smallscreen;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.AbsoluteScreenLocateable;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoImage;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.ScreenLocation;
import org.geogebra.common.util.MyMath;
import org.geogebra.common.util.debug.Log;
/**
* Adjusts position of absolutely positioned elements (texts, buttons, lists,
* images, checkboxes)
*
* @author Laszlo
*
*/
public class LayoutAbsoluteGeos {
private static final int Y_GAP = 5;
private static final int X_GAP = 5;
private List<AbsoluteScreenLocateable> originals = new ArrayList<AbsoluteScreenLocateable>();
private List<AbsoluteScreenLocateable> all = new ArrayList<AbsoluteScreenLocateable>();
private List<AbsoluteScreenLocateable> moveable = new ArrayList<AbsoluteScreenLocateable>();
private final EuclidianView view;
private final static AbsoluteGeoComparator comparatorX = new AbsoluteGeoComparator(
false);
private final static AbsoluteGeoComparator comparatorY = new AbsoluteGeoComparator(
true);
// Buttons should be collected only once.
private boolean collected = false;
private static class AbsoluteGeoComparator
implements Comparator<AbsoluteScreenLocateable> {
private boolean vertical;
public AbsoluteGeoComparator(boolean vertical) {
this.vertical = vertical;
}
@Override
public int compare(AbsoluteScreenLocateable o1,
AbsoluteScreenLocateable o2) {
if (vertical) {
int y1 = o1.getAbsoluteScreenLocY();
int y2 = o2.getAbsoluteScreenLocY();
if (y1 == y2) {
return 0;
}
return Kernel.isGreater(y1, y2) ? -1 : 1;
}
int x1 = o1.getAbsoluteScreenLocX();
int x2 = o2.getAbsoluteScreenLocX();
if (x1 == x2) {
return 0;
}
return Kernel.isGreater(x1, x2) ? -1 : 1;
}
}
/**
* @param view
* view
*/
public LayoutAbsoluteGeos(EuclidianView view) {
this.view = view;
}
public void add(AbsoluteScreenLocateable absGeo) {
if (view.isVisibleInThisView(absGeo)) {
originals.add(absGeo);
}
}
/**
* Reset all the collected geos to their original screen location that come
* from file.
*/
public void reset() {
Log.debug("[LayoutAbsoluteGeos] reset ");
for (AbsoluteScreenLocateable loc : originals) {
ScreenLocation screenLoc = ((GeoElement) loc).getScreenLocation();
if (screenLoc != null) {
loc.setAbsoluteScreenLoc(screenLoc.getX(), screenLoc.getY());
}
}
}
public void clear() {
all.clear();
moveable.clear();
}
public void restart() {
clear();
collected = false;
originals.clear();
}
private void sortX() {
Collections.sort(all, comparatorX);
}
private void sortY() {
Collections.sort(all, comparatorY);
}
// private static String buttonDetails(AbsoluteScreenLocateable absGeo,
// EuclidianView view) {
// return absGeo + " (" + absGeo.getAbsoluteScreenLocX() + ", "
// + absGeo.getAbsoluteScreenLocY() + "), " + absGeo.getTotalWidth(view)
// + "x"
// + absGeo.getTotalHeight(view);
// }
// private void debugButtons(String msg,
// List<AbsoluteScreenLocateable> buttons) {
// Log.debug("[LayoutButtons] " + msg);
// for (int idx = 0; idx < buttons.size(); idx++) {
// AbsoluteScreenLocateable absGeo = buttons.get(idx);
// Log.debug(
// "[LayoutButtons] " + idx + ". " + buttonDetails(absGeo, view));
//
// }
//
// }
public void apply() {
all.clear();
all.addAll(originals);
// debugButtons("initial: ", all);
applyHorizontally();
// debugButtons("after horizontally: ", all);
applyVertically();
// debugButtons("after vertically: ", all);
}
private void applyVertically() {
sortY();
divideY();
if (moveable.isEmpty()) {
// All buttons is on screen, nothing to do.
return;
}
ArrayList<GRectangle> usedPositions = new ArrayList<GRectangle>();
boolean moveNeeded = false;
for (AbsoluteScreenLocateable absGeo : moveable) {
final int x = absGeo.getAbsoluteScreenLocX();
int geoHeight = absGeo.getTotalHeight(view);
int geoWidth = absGeo.getTotalWidth(view);
int y = maxUnusedY(usedPositions, x, x + geoWidth,
view.getHeight());
y -= geoHeight + Y_GAP;
int yCorner;
if (bottomAnchor(absGeo)) {
yCorner = y + geoHeight;
} else {
yCorner = y;
}
if (yCorner < absGeo.getAbsoluteScreenLocY() + Y_GAP) {
moveNeeded = true;
}
if (moveNeeded) {
y = Math.min(absGeo.getAbsoluteScreenLocY(), yCorner);
setAbsoluteScreenLoc(absGeo, x, y);
usedPositions.add(AwtFactory.getPrototype().newRectangle(x, y,
geoWidth, geoHeight));
absGeo.update();
}
}
}
private static boolean bottomAnchor(AbsoluteScreenLocateable absGeo) {
return absGeo.isGeoText() || absGeo.isGeoImage();
}
private void applyHorizontally() {
sortX();
divideX();
if (moveable.isEmpty()) {
// All buttons is on screen, nothing to do.
return;
}
ArrayList<GRectangle> usedPositions = new ArrayList<GRectangle>();
boolean moveNeeded = false;
for (AbsoluteScreenLocateable absGeo : moveable) {
final int y = absGeo.getAbsoluteScreenLocY();
int x = maxUnusedX(usedPositions, y,
y + absGeo.getTotalHeight(view), view.getWidth());
x -= absGeo.getTotalWidth(view) + X_GAP;
if (x < absGeo.getAbsoluteScreenLocX() + X_GAP) {
moveNeeded = true;
}
if (moveNeeded) {
x = Math.min(absGeo.getAbsoluteScreenLocX(), x);
setAbsoluteScreenLoc(absGeo, x, y);
usedPositions.add(AwtFactory.getPrototype().newRectangle(x, y,
absGeo.getTotalWidth(view),
absGeo.getTotalHeight(view)));
absGeo.update();
}
}
}
private static void setAbsoluteScreenLoc(AbsoluteScreenLocateable absGeo,
int x,
int y) {
if (absGeo instanceof GeoBoolean) {
((GeoBoolean) absGeo).setAbsoluteScreenLoc(x, y, true);
} else {
absGeo.setAbsoluteScreenLoc(x, y);
}
}
private static int maxUnusedX(ArrayList<GRectangle> usedPositions, int yTop,
int yBottom, int max0) {
int max = max0;
for (GRectangle rect : usedPositions) {
if (MyMath.intervalsIntersect(rect.getMinY(), rect.getMaxY(), yTop,
yBottom)) {
if (max > rect.getMinX()) {
max = (int) rect.getMinX();
}
}
}
return max;
}
private static int maxUnusedY(ArrayList<GRectangle> usedPositions,
int xLeft,
int xRight, int max0) {
int max = max0;
for (GRectangle rect : usedPositions) {
if (MyMath.intervalsIntersect(rect.getMinX(), rect.getMaxX(), xLeft,
xRight)) {
if (max > rect.getMinY()) {
max = (int) rect.getMinY();
}
}
}
return max;
}
private void divideX() {
moveable.clear();
for (AbsoluteScreenLocateable absGeo : all) {
if (isHorizontallyOnScreen(absGeo)) {
moveable.add(absGeo);
}
}
}
private void divideY() {
moveable.clear();
// sumOfHeight = 0;
for (AbsoluteScreenLocateable absGeo : all) {
if (isVerticallyOnScreen(absGeo)) {
moveable.add(absGeo);
}
}
}
private boolean isHorizontallyOnScreen(AbsoluteScreenLocateable absGeo) {
int x = absGeo.getAbsoluteScreenLocX();
int width = absGeo.getTotalWidth(view);
return view.getSettings().getFileWidth() == 0
|| x + width < view.getSettings().getFileWidth();
}
private boolean isVerticallyOnScreen(AbsoluteScreenLocateable absGeo) {
int y = absGeo.getAbsoluteScreenLocY();
int height = absGeo.getTotalHeight(view);
return view.getSettings().getFileHeight() == 0
|| y + (bottomAnchor(absGeo) ? 0 : height) < view.getSettings()
.getFileHeight();
}
/**
* @return whether widget collection was finished
*/
public boolean isCollected() {
return collected;
}
/**
* @param collected
* whether widget collection was finished
*/
public void setCollected(boolean collected) {
if (originals.isEmpty()) {
return;
}
this.collected = collected;
}
/**
*
* @param geo
* to check.
* @return if geo can be handled with this class or not.
*/
public boolean match(GeoElement geo) {
if (!view.isVisibleInThisView(geo) || !geo.isEuclidianVisible()) {
return false;
}
return geo.isGeoButton()
|| (geo.isGeoBoolean() && geo.isEuclidianShowable())
|| (geo.isGeoText() && geo.isVisible()
&& ((AbsoluteScreenLocateable) geo)
.isAbsoluteScreenLocActive())
|| (geo.isGeoList() && ((GeoList) geo).drawAsComboBox())
|| (geo.isGeoImage() && ((AbsoluteScreenLocateable) geo)
.isAbsoluteScreenLocActive()
&& !((GeoImage) geo).isInBackground());
}
}