package org.geogebra.common.kernel; import java.util.Iterator; import java.util.SortedSet; import org.geogebra.common.cas.GeoGebraCAS; import org.geogebra.common.gui.util.RelationMore; import org.geogebra.common.javax.swing.RelationPane; import org.geogebra.common.javax.swing.RelationPane.RelationRow; import org.geogebra.common.kernel.RelationNumerical.Report; import org.geogebra.common.kernel.RelationNumerical.Report.RelationCommand; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.prover.AlgoAreCollinear; import org.geogebra.common.kernel.prover.AlgoAreConcurrent; import org.geogebra.common.kernel.prover.AlgoAreConcyclic; import org.geogebra.common.kernel.prover.AlgoAreCongruent; import org.geogebra.common.kernel.prover.AlgoAreEqual; import org.geogebra.common.kernel.prover.AlgoAreParallel; import org.geogebra.common.kernel.prover.AlgoArePerpendicular; import org.geogebra.common.kernel.prover.AlgoIsOnPath; import org.geogebra.common.kernel.prover.AlgoProve; import org.geogebra.common.kernel.prover.AlgoProveDetails; import org.geogebra.common.main.App; import org.geogebra.common.main.Localization; import org.geogebra.common.plugin.Event; import org.geogebra.common.plugin.EventType; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * Compares two objects, first numerically, then symbolically (when the * "More..." button is pressed). The original contents of this file has been * moved into RelationNumerical.java and extensively rewritten. * * @author Zoltan Kovacs <zoltan@geogebra.org> */ public class Relation { /** * @param app * currently used application * @param ra * first object * @param rb * second object * @param rc * third object (optional, can be null) * @param rd * forth object (optional, can be null) * * @author Zoltan Kovacs <zoltan@geogebra.org> */ public static void showRelation(final App app, final GeoElement ra, final GeoElement rb, final GeoElement rc, final GeoElement rd) { // Forcing CAS to load. This will be essential for the web version // to run the Prove[Are...] commands with getting no "undefined": GeoGebraCAS cas = (GeoGebraCAS) ra.getKernel().getGeoGebraCAS(); try { cas.getCurrentCAS().evaluateRaw("1"); } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); } // Creating Relation popup window: RelationPane tablePane = app.getFactory().newRelationPane(); // Computing numerical results and collecting them alphabetically: SortedSet<Report> relInfosAll = RelationNumerical.sortAlphabetically( new RelationNumerical(app.getKernel()).relation(ra, rb, rc, rd)); // Collecting information for showing them in the popup window: Iterator<Report> it = relInfosAll.iterator(); int rels = relInfosAll.size(); String[] relInfos = new String[rels]; RelationCommand[] relAlgos = new RelationCommand[rels]; Boolean[] relBools = new Boolean[rels]; int i = 0; while (it.hasNext()) { Report r = it.next(); relInfos[i] = r.stringResult; relAlgos[i] = r.symbolicCheck; relBools[i] = r.boolResult; i++; } final RelationRow rr[] = new RelationRow[rels]; for (i = 0; i < rels; i++) { rr[i] = new RelationRow(); final String relInfo = relInfos[i].replace("\n", "<br>"); // First information shown (result of numerical checks): rr[i].setInfo("<html>" + relInfo + "<br>" + app.getLocalization().getMenu("CheckedNumerically") + "</html>"); final RelationCommand relAlgo = relAlgos[i]; RelationMore rm = new RelationMore() { @Override public void action(RelationPane table, int row) { final RelationRow rel = new RelationRow(); app.setWaitCursor(); Boolean result = checkGenerally(relAlgo, ra, rb, rc, rd); Localization loc = ra.getConstruction().getApplication() .getLocalization(); String and = loc.getMenu("Symbol.And").toLowerCase(); rel.setInfo("<html>"); if (result != null && !result) { // Prove==false rel.setInfo(rel.getInfo() + relInfo + "<br><b>" + loc.getMenu("ButNotGenerallyTrue") + "</b>"); app.setDefaultCursor(); } else { // We don't show the second information unless // ProveDetails is unsuccessful. // Third info start: String[] ndgResult = getNDGConditions(relAlgo, ra, rb, rc, rd); app.setDefaultCursor(); // This style is defined in the CSS. It is harmless in // desktop but // helpful to show nice list look in web: String liStyle = "class=\"RelationTool\""; // Third information shown (result of ProveDetails // command): if (ndgResult.length == 1) { // ProveDetails=={true} or =={false} or ==undefined rel.setInfo( rel.getInfo() + relInfo + "<br><b>"); if ("".equals(ndgResult[0])) { // ProveDetails==undefined if (result != null && result) { // Using Prove's result (since ProveDetails // couldn't find any interesting): rel.setInfo( rel.getInfo() + loc.getMenu("GenerallyTrue")); } else { // Prove==ProveDetails==undefined rel.setInfo( rel.getInfo() + loc .getMenu("PossiblyGenerallyTrue")); } } else if ("1".equals(ndgResult[0])) { // ProveDetails=={true} rel.setInfo(rel.getInfo() + loc.getMenu("AlwaysTrue")); } else { // "0" Log.error( "Internal error in prover: Prove==true <-> ProveDetails==false"); rel.setInfo(rel.getInfo() + loc.getMenu("ButNotGenerallyTrue")); } rel.setInfo(rel.getInfo() + "</b>"); } else { int ndgs = ndgResult.length; if ((ndgs == 2) && ((Unicode.ellipsis + "") .equals(ndgResult[1]))) { // UnderCertainConditionsA rel.setInfo(rel.getInfo() + loc.getPlain( "UnderCertainConditionsA", "<ul><li " + liStyle + ">" + relInfo + "</ul>")); } else { // GenerallyTrueAcondB StringBuilder conds = new StringBuilder("<ul>"); for (int j = 1; j < ndgs; ++j) { conds.append("<li "); conds.append(liStyle); conds.append(">"); conds.append(ndgResult[j]); if ((j < ndgs - 1)) { conds.append(" "); conds.append(and); } } conds.append("</ul>"); rel.setInfo(rel.getInfo() + loc .getPlain("GenerallyTrueAcondB", "<ul><li " + liStyle + ">" + relInfo + "</ul>", conds.toString())); } } } rel.setInfo(rel.getInfo() + "</html>"); rel.setCallback(null); table.updateRow(row, rel); } }; if (relBools[i] != null && relBools[i] && relAlgos[i] != null) { rr[i].setCallback(rm); } } // just send first row to event app.dispatchEvent(new Event(EventType.RELATION_TOOL, null, rr[0].getInfo())); tablePane.showDialog(app.getLocalization().getCommand("Relation"), rr, ra.getConstruction().getApplication()); } /** * Tries to compute if a geometry statement holds generally. * * @param command * Are... command * @param g1 * first object * @param g2 * second object * @param g3 * third object (optional) * @param g4 * forth object (optional) * @return true if statement holds generally, false if it does not hold, * null if cannot be decided by GeoGebra * * @author Zoltan Kovacs <zoltan@geogebra.org> */ final public static Boolean checkGenerally(RelationCommand command, GeoElement g1, GeoElement g2, GeoElement g3, GeoElement g4) { Boolean ret = null; Construction cons = g1.getConstruction(); GeoElement root = new GeoBoolean(cons); AlgoElement ae = null; try { switch (command) { case AreEqual: ae = new AlgoAreEqual(cons, g1, g2); break; case AreCongruent: ae = new AlgoAreCongruent(cons, g1, g2); break; case AreParallel: ae = new AlgoAreParallel(cons, g1, g2); break; case ArePerpendicular: ae = new AlgoArePerpendicular(cons, g1, g2); break; case IsOnPath: if ((g1 instanceof GeoPoint) && (g2 instanceof Path)) { ae = new AlgoIsOnPath(cons, (GeoPoint) g1, (Path) g2); } else if ((g2 instanceof GeoPoint) && (g1 instanceof Path)) { ae = new AlgoIsOnPath(cons, (GeoPoint) g2, (Path) g1); } break; case AreConcyclic: ae = new AlgoAreConcyclic(cons, (GeoPoint) g1, (GeoPoint) g2, (GeoPoint) g3, (GeoPoint) g4); break; case AreCollinear: ae = new AlgoAreCollinear(cons, (GeoPoint) g1, (GeoPoint) g2, (GeoPoint) g3); break; case AreConcurrent: ae = new AlgoAreConcurrent(cons, (GeoLine) g1, (GeoLine) g2, (GeoLine) g3); break; } } catch (Exception ex) { return ret; // there was an error during Prove } if (ae == null) { return ret; // which is null here } root.setParentAlgorithm(ae); AlgoProve ap = new AlgoProve(cons, null, root); ap.compute(); GeoElement[] o = ap.getOutput(); GeoBoolean ans = ((GeoBoolean) o[0]); if (ans.isDefined()) { ret = ans.getBoolean(); } root.remove(); o[0].remove(); return ret; } /** * Tries to compute a necessary condition for a given statement to hold. * * @param command * Are... command * @param g1 * first object * @param g2 * second object * @param g3 * third object (may be null) * @param g4 * forth object (may be null) * * @return [""]: undefined, ["0"]: false, ["1"]: always true, ["1", cond1, * cond2, ...]: true under cond1 and cond2 and ... * * @author Zoltan Kovacs <zoltan@geogebra.org> * */ final public static String[] getNDGConditions(RelationCommand command, GeoElement g1, GeoElement g2, GeoElement g3, GeoElement g4) { Construction cons = g1.getConstruction(); GeoElement root = new GeoBoolean(cons); AlgoElement ae = null; String[] ret; try { switch (command) { case AreCongruent: ae = new AlgoAreCongruent(cons, g1, g2); break; case AreEqual: ae = new AlgoAreEqual(cons, g1, g2); break; case AreParallel: ae = new AlgoAreParallel(cons, g1, g2); break; case ArePerpendicular: ae = new AlgoArePerpendicular(cons, g1, g2); break; case IsOnPath: if ((g1 instanceof GeoPoint) && (g2 instanceof Path)) { ae = new AlgoIsOnPath(cons, (GeoPoint) g1, (Path) g2); } else if ((g2 instanceof GeoPoint) && (g1 instanceof Path)) { ae = new AlgoIsOnPath(cons, (GeoPoint) g2, (Path) g1); } break; case AreConcyclic: ae = new AlgoAreConcyclic(cons, (GeoPoint) g1, (GeoPoint) g2, (GeoPoint) g3, (GeoPoint) g4); break; case AreCollinear: ae = new AlgoAreCollinear(cons, (GeoPoint) g1, (GeoPoint) g2, (GeoPoint) g3); break; case AreConcurrent: ae = new AlgoAreConcurrent(cons, (GeoLine) g1, (GeoLine) g2, (GeoLine) g3); break; } } catch (Exception ex) { ret = new String[1]; ret[0] = ""; // on error: undefined (UNKNOWN) return ret; } if (ae == null) { ret = new String[1]; ret[0] = ""; // undefined (UNKNOWN) return ret; } root.setParentAlgorithm(ae); AlgoProveDetails ap = new AlgoProveDetails(cons, root, true); ap.compute(); GeoElement[] o = ap.getOutput(); GeoList list = ((GeoList) o[0]); // Turning the output of ProveDetails into an array: if (list.size() >= 2) { GeoList conds = (GeoList) list.get(1); int condsSize = conds.size(); ret = new String[condsSize + 1]; for (int i = 0; i < condsSize; ++i) { String cond = conds.get(i) .toString(StringTemplate.defaultTemplate); // Removing quotes: ret[i + 1] = cond.substring(1, cond.length() - 1); } } else { ret = new String[1]; } if (list.size() != 0) { Boolean ans = ((GeoBoolean) list.get(0)).getBoolean(); if (!((GeoBoolean) list.get(0)).isDefined()) { ret[0] = ""; // undefined (UNKNOWN) } else if (ans) { ret[0] = "1"; // TRUE } else { ret[0] = "0"; // FALSE } } else { ret[0] = ""; // undefined (UNKNOWN) } root.remove(); o[0].remove(); return ret; } }