Package ketUI.modes

Source Code of ketUI.modes.NormalMode

/*
* Copyright (C) 2011  Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package ketUI.modes;

import java.util.*;
import javax.swing.*;
import java.io.*;
import java.awt.event.KeyEvent;

import geom.Position;

import ket.*;
import ket.display.HTMLTools;
import ket.display.box.Box;
import ket.math.*;
import ket.math.convert.*;
import ket.math.purpose.*;
import ket.treeDiff.*;
import ketUI.Album;
import ketUI.Clipboard;
import ketUI.Document;
import ketUI.DocumentManager;
import ketUI.Ket;
import ketUI.MenuEventHandler;
import ketUI.chord.Chord;
import ketUI.chord.KeyPress;
import ketUI.chord.KeyboardEventHandler;
import ketUI.chord.Macros;
import ketUI.panel.KetPanel;


public class NormalMode {

  public static final String ATAN_DASH_FILE           = "/data/atanDashTutorial.template";
  public static final String COMMANDS_REFERENCE_FILE  = "/data/chords.template";
  public static final String CONSTANTS_FILE           = "/data/constants.template";
  public static final String CONTOUR_INTEGRAL_FILE    = "/data/contour.template";
  public static final String DERIVATIVES_FILE         = "/data/derivatives.template";
  public static final String DE_FILE                  = "/data/differentialEquations.template";
  public static final String EDITING_FILE             = "/data/advancedEditing.template";
  public static final String FOURIER_TRANSFORMS_FILE  = "/data/fourier.template";
  public static final String FUNCTIONS_REFERENCE_FILE = "/data/functions.template";
  public static final String INTEGRALS_FILE           = "/data/integral.template";
  public static final String KEPLER_FILE              = "/data/keplerTutorial.template";
  public static final String LAPLACE_FILE             = "/data/Laplace.template";
  public static final String MAXWELL_FILE             = "/data/maxwellTutorial.template";
  public static final String OVERVIEW_FILE            = "/data/overview.template";
  public static final String PHYSICS_FILE             = "/data/physics.template";
  public static final String SIMPLE_FILE              = "/data/simple.template";
  public static final String STATS_FILE               = "/data/stats.template";
  public static final String SYMBOLS_REFERENCE_FILE   = "/data/symbols.template";
  public static final String TRIG_IDENTITIES_FILE     = "/data/trigIdentities.template";
  public static final String TUTORIAL_FILE            = "/data/tutorial.template";
  public static final String VECTOR_FILE              = "/data/vectors.template";

  public static final String[] FILES = {
    CONSTANTS_FILE, CONTOUR_INTEGRAL_FILE, DERIVATIVES_FILE, DE_FILE,
    FOURIER_TRANSFORMS_FILE, INTEGRALS_FILE, LAPLACE_FILE, PHYSICS_FILE,
    SIMPLE_FILE, STATS_FILE, TRIG_IDENTITIES_FILE, VECTOR_FILE};

  final Modes modes;
  final NormalMode normalMode;
  final MathCollection mathCollection;
  final KeyboardEventHandler keyboardEventHandler;
  final Cycle cycle;

  Vector<Argument> referenceMatches = new Vector<Argument>(); // i.e. empty and not null.


  public NormalMode(Modes modes) {
    this.modes = modes;
    this.normalMode = modes.getNormalMode();
    this.mathCollection = modes.getMathCollection();
    this.keyboardEventHandler = getDocument().getKeyboardEventHandler();
    this.cycle = new Cycle(mathCollection.getKnownArguments());
  }

  ////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////// UTILITY METHODS ////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////


  /////////////
  // FOLDING //
  /////////////

  // :normal 'h', 'o', 'te'.
  public void expandLeft() {
    getCursor().selectLeft(); // 'h'
    getCursor().selectInOrOutRight(); // 'o'
    getSelection().duplicateSwapAncestors(); // 'te'
    getCursor().selectInOrOutRight(); // 'o'
    getCursor().selectInOrOutRight(); // 'o'
  }

  // :normal 'l', 'i', 'te'.
  public void expandRight() {
    getCursor().selectRight(); // 'l'
    getCursor().selectInOrOutLeft(); // 'i'
    getSelection().duplicateSwapAncestors()// 'te'
    getCursor().selectInOrOutLeft(); // 'i'
    getCursor().selectInOrOutLeft(); // 'i'
  }

  public boolean closeAncestorFold() {
    if (getCurrent() instanceof Branch) {
      Equation equation = getCursor().getEquation();
      equation.setVisibleRoot((Branch) getCurrent());
    } else {
      modes.error("Cannot fold a non-branch.");
    }
    return false;
  }

  public void openFold() {
    openAncestorFold();
    openDescendentFold();
  }

  public void openAncestorFold() {
    if (getCurrent() instanceof Branch) {
      getCursor().getEquation().unfold();
    }
  }

  public void closeDescendentFold() {
    if (getSelection().areMultipleEquationsSelected()) {
      getSelection().addIntermediaryParent(Function.VECTOR);
    }
    getCurrent().setVisible(false);
  }

  public void openDescendentFold() {
    getCurrent().setVisible(true);
  }

  public void selectInOrOutLeft(Chord chord) {
    getSelection().shatterText();
    if (chord.countChanged()) {
      int count = chord.getCounts();
      Address address = new Address(count);
      chord.setLoopSkipped(true);
      Highlight highlight = mathCollection.getHighlight();
      Argument relative = highlight.relativeAddress(address);
      if (relative!=null) {
        getSelection().setOnly(relative);
      }
    } else {
      getCursor().selectInOrOutLeft();
    }
  }

  public void selectInOrOutRight(Chord chord) {
    getSelection().shatterText();
    if (chord.countChanged()) {
      int count = chord.getCounts();
      Address address = new Address(count);
      chord.setLoopSkipped(true);
      Highlight highlight = mathCollection.getHighlight();
      Argument relative = highlight.relativeAddress(address);
      if (relative!=null) {
        getSelection().setOnly(relative);
      }
    } else {
      getCursor().selectInOrOutRight();
    }
  }

  public void selectLeft() {
    if (mathCollection.isColumnSelectionSet()) {
      getCursor().selectLeftByAddress();
    } else {
      getCursor().selectLeft();
    }
  }

  public void selectRight() {
    if (mathCollection.isColumnSelectionSet()) {
      getCursor().selectRightByAddress();
    } else {
      getCursor().selectRight();
    }
  }

  public void selectUp() {
    if (mathCollection.isColumnSelectionSet()) {
      getCursor().selectUpByAddress();
    } else {
      getCursor().selectUp();
    }
  }

  public void selectDown() {
    if (mathCollection.isColumnSelectionSet()) {
      getCursor().selectDownByAddress();
    } else {
      getCursor().selectDown();
    }
  }

  private RangeSelection getRangeSelection() {
    return mathCollection.getRangeSelection();
  }

  /**
   * Append a clone after the current selection within a new intermediate
   * branch of a use-specified operation.
   */
  public void argumentClone(Chord chord) {
    Branch intermediate = getCurrent().addIntermediaryParent(Function.UNKNOWN);
    int n = 2;
    if (chord.countChanged()) {
      n = chord.getCounts();
      chord.setLoopSkipped(true);
    }
    for (int i=1; i<n; i++) { // Include the original in the count.
      Argument clone = Argument.cloneArgument(getCurrent());
      intermediate.append(clone);
    }
    getSelection().setOnly(intermediate);
    chord.setComplete(false);
    modes.setDocumentState(DocumentState.QUICK_RENAME);
  }

  public void equate() {
    Ket.out.printf("%60sDEBUG CYPHER:\n", "");
    Argument root = getCurrent().getRoot();
    if (Like.lacksForm(root, Function.EQUALS, 2) && Like.lacksForm(root, Function.TO, 2))
      return;
    Branch branch = (Branch) root;
    Argument clone = Argument.cloneArgument(branch.firstChild());
    IdentityHashMap<Argument, Argument> t = clone.cypher(branch.lastChild());
    Branch v = new Branch(Function.VECTOR);
    for (Argument k : t.keySet()) {
      Argument from = Argument.cloneArgument(k);
      Argument to = Argument.cloneArgument(t.get(k));
      v.append(new Branch(Function.TO, from, to));
    }
    EquationList el = root.getEquationList();
    el.addAfter(root.getEquation(), new Equation(v));
  }

  public boolean recursiveDifferentiate() {
    Ket.out.println("[recursiveDifferentiate-0]" + getCurrent());
    if (getCurrent().isToken()) return false;
    boolean change = true;
    getSelection().simplify();
    EquationList el = getCurrent().getEquationList();
    Address address = getCurrent().getAddress();
    while (change && getCurrent().isBranch()) {
      Ket.out.println(" ============================= ");
      Ket.out.println("    before = " + getCurrent() + "\t" + getCurrent().getAddress());
      change = recursiveDifferentiate(getCurrent().asBranch());
      getSelection().setCurrent(el.relativeAddress(address));
      Ket.out.println("    dash   = " + getCurrent() + "\t" + getCurrent().getAddress());
      getSelection().simplify();
      getSelection().setCurrent(el.relativeAddress(address));
      Ket.out.println("    simple = " + getCurrent() + "\t" + getCurrent().getAddress());
      Ket.out.println(" ============================= ");
      for (int i=0; i<5; i++) Ket.out.println();
    }
    return true;
  }

  public boolean recursiveDifferentiate(Branch next) {
    Ket.out.println("[recursiveDifferentiate-1]" + next);
    getSelection().setOnly(next);
    Ket.out.println("Recur differentiate:" + getCurrent());
    if (expandDash(next)) {
      Ket.out.println("\texpand dash -> true $" + getCurrent());
      return true;
    } else if (dash(next)) {
      Ket.out.println("\tdash -> true $" + getCurrent());
      return true;
    } else if (Type.transparent(next.getFunction())) {
      return mapThrough(next);
    } else {
      return false;
    }
  }

  private boolean dash(Branch next) {
    Ket.out.println("[dash]" + next);
    if (Like.firstDerivativeLike(next)==null) return false;
    Argument derivative = Transform.differentiate(next);
    if (derivative==null) return false;
    Ket.out.println("DASH: " + next); //*
    Ket.out.println("\t -> " + derivative); //*
    getSelection().replace(derivative);
    Ket.out.println("\tselection=" + getCurrent());
    return true;
  }

  private boolean expandDash(Branch next) {
    Ket.out.println("[expand dash]" + next);
    Argument subject = next.asBranch().firstChild();
    if (subject==null || subject.size()<1) return false;
    Argument end = subject.asBranch().firstChild();
    if (Like.isLinearDerivative(next, end)) { // derivative(a+y+..., x)
      getSelection().setCurrent(end);
      getSelection().duplicateSwapAncestors();
      return true;
    } else if (Like.isProductDerivative(next, end)) {  // (a*b)'x -> a'x*b + a*b'x
      getSelection().replace(Transform.applyProductRule(next));
      return true;
    } else if (Like.isQuotientDerivative(next, end)) { // (a/b)'x -> (a'x*b-a*b'x)/b^2
      Transform.applyQuotientRule(getSelection(), next, end);
      return true;
    } else if (Like.isCompositionDerivative(next, end)) { // TODO: Clean up.
      Ket.out.println(" --- <composition> --- ");
      Ket.out.println("before: "+next);
      Transform.applyChainRule(getSelection(), next, (Branch) end, mathCollection.getKnownArguments());
      next = getCurrent().asBranch(); // (f(q)'q)@(q=...) * g(x)'x
      Ket.out.println("chain rule: " + next);
      // next = (f(q)'q)@(q=?)*? so solve for f(q)'q then substitute.
      try {
        Branch firstGrandChild = next.firstChild().asBranch().firstChild().asBranch(); // f(q)'q
        Ket.out.println("grandchild = " + firstGrandChild);
        getSelection().setOnly(firstGrandChild);
        recursiveDifferentiate(firstGrandChild);
        Ket.out.println("dash = " + getCurrent());
        Branch range = next.asBranch().firstChild().asBranch(); // h(q)'q=...
        Ket.out.println("range: " + range);
        getSelection().setCurrent(range);
        Transform.expandRange(getSelection(), range);
      } catch (NullPointerException e) {
        Ket.out.println("null pointer: " + next);
        return false;
      }
      Ket.out.println("substt: " + getCurrent());
      setCurrent(next); //?
      Ket.out.println("next = " + next);
      Ket.out.println(" --- </composition> --- ");
      return true;
    } else {
      return false;
    }
  }

  private boolean mapThrough(Branch next) {
    Ket.out.println("[map through]" + next);
    boolean changed = false;
    for (Argument child : next.getChildren()) {
      if (child.isBranch()) {
        Ket.out.println("CHILD: " + child);
        changed |= recursiveDifferentiate(child.asBranch());
      }
    }
    return changed;
  }

  ////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////// GENERAL ////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////

  public void toggleNetworkDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleNetworkDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void toggleGraphDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleGraphDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void toggleHistoryDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleHistoryDisplay();
  }

  public void toggleListDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleListDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void togglePerspectiveDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.togglePerspectiveDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void toggleScatterDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleScatterDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void toggleAnimatedDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleAnimatedDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void toggleGridDisplay() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.toggleGridDisplay();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void cygleDisplayMode() {
    KetPanel ketPanel = getKetPanel();
    ketPanel.cygleDisplayMode();
    ketPanel.fromMiddle(getCursor().getEquation().getEquationIndex());
    MenuEventHandler menuEventHandler = getDocument().getFrameManager().getMenuEventHandler();
    if (menuEventHandler!=null) {
      menuEventHandler.updateMultipleEquations();
    }
  }

  public void openDocument(String filename) {
    if ( ! getDocument().getFrameManager().isStandAlone() ) {
      Ket.out.println("[Can't open "+filename+"]");
      return;
    }
    DocumentManager documentManager = getDocument().getDocumentManager();
    Document document = new Document(documentManager, null, filename);
    document.getKetPanel().toggleGridDisplay();
    if (documentManager!=null) {
      documentManager.addDocument(document);
    }
  }

  public boolean openLink() {
    if ( ! getDocument().getFrameManager().isStandAlone() ) {
      return false;
    }
    Branch branch = getCursor().asBranch();
    if (branch==null || branch.getFunction()!=Function.LINK) {
      branch = getCurrent().getParentBranch();
      getSelection().setOnly(branch);
    }
    if (branch==null || branch.getFunction()!=Function.LINK) {
      Ket.out.println(" !!! Open link reqiures the current or parent branch to be a link !!! ");
      return false;
    }
    DocumentManager documentManager = getDocumentManager();
    if (documentManager==null) {
      Ket.out.println(" !!! Can't follow links from within another GUI !!! ");
      return false;
    }
    switch (branch.size()) {
      case 1: // format: link(pathString)
        openLinkDocument(recognizePath(0), documentManager, null);
        return true; // TODO: Return true if the file actually loaded as required.
      case 2: // format: link(label, pathString)
        openLinkDocument(recognizePath(1), documentManager, null);
        return true; // TODO: Return true if the file actually loaded as required.
      case 3: // format: link(label, pathString, lineNumber)
        openLinkDocument(recognizePath(1), documentManager, getLocation());
        return true;
      default:
        return false;
    }
  }

  // Recursively perform monte carlo integration until interupted.
  public void monte() {
    Ket.out.println("<Monte>");
    Argument current = getCurrent();
    if (current.getFunction()==Function.INTEGRAL && current.size()==4) { //> Like.numericIntegralLike(current).
      Monte monte = new Monte(getCurrent().asBranch());
      Thread thread = new Thread(monte);
      thread.start();
      Ket.out.println("</Monte>");
    }
  }
  class Monte implements Runnable {
    Branch integral; // required?
    Argument subject;
    Token x;
    Double from;
    Double to;
    double total;
    double total2;
    int n;
    Address address;
    private Monte() { }
    public Monte(Argument integral) {
      Ket.out.println("[monte...]");
      this.integral = (Branch) Argument.cloneArgument(integral);
      n = 0;
      total = 0.0;
      total2 = 0.0;
      address = integral.getAddress();
      process();
    }
    private double sample() {
      return Math.random()*Math.abs(to-from) + Math.min(from, to);
    }
    private double eval() {
      return Calculate.eval(Argument.cloneArgument(subject), sample(), Argument.cloneArgument(x)); // Are the clones required?
    }
    private void process() {
      from    = Like.getDouble(integral.getChild(0));
      to      = Like.getDouble(integral.getChild(1));
      subject = Argument.cloneArgument(integral.getChild(2));
      x       = Argument.cloneArgument(integral.getChild(3)).asToken();
      Ket.out.println("integrate: " + from + "->" + to + " a subject " + subject + " wrt " + x);
    }
    public DoubleValue integrate() { // WARNING: Returns the fractional error change not the integral!
      // Iterate over a time limit.
      for (int i=0; i<1000; i++) {
        n += 1;
        double y = eval();
        total += y;
        total2 += y*y;
      }
      double scale = (to-from)/n;
      double mean = total/n;
      double stdev = Math.sqrt(total2/n - mean*mean); // sqrt(E(X^2) - E(X)^2) -> integral
      double result = total*scale;
      double error = stdev*scale;
      Ket.out.printf("\rintegral = %13.7g \\pm %13.7g  %s&%s\t", result, error, address, getCurrent().getAddress());
      DoubleValue doubleValue = new DoubleValue(result);
      doubleValue.setPrecision((int) Math.floor(Math.log10(error))); // 0.012 -> -2
      return doubleValue;
    }
    public void run() {
      Ket.out.println("[sampling...]");
      DoubleValue doubleValue = integrate();
      while (address.equals(getCurrent().getAddress())) {
        getSelection().replace(new Token(doubleValue));
        getKetPanel().updateAndRepaint();
        try {
          Thread.sleep(100);
        } catch (InterruptedException ie) {
          Ket.out.println(ie);
          return;
        }
        doubleValue = integrate();
      }
      Ket.out.println("\n[...done]");
    }
  }

  /**
   * Open a given document at a location specified by the given state:
   * either an instance of text, word or integer value.  A null location
   * specifies that the program should select the start of the document.
   */
  private boolean openLinkDocument(String filename, DocumentManager documentManager, State location) {
    if ( ! getDocument().getFrameManager().isStandAlone() ) {
      return false;
    }
    if (filename==null) {
      Ket.out.println(" !!! Unexpected filename !!! ");
      return false;
    }
    // TODO: Check that the filename exists before actually openning the window.
    Document document = null;
    String label = Like.getString(location);
    Integer equationNumber = Like.getInteger(location);
    if (location==null) {
      document = new Document(documentManager, null, filename);
    } else if (equationNumber!=null) {
      int equationIndex = equationNumber-1;
      document = new Document(documentManager, null, filename, equationIndex);
    } else if (label!=null) {
      // TODO: Check that the filename exists before actually openning the window.
      document = new Document(documentManager, null, filename, label);
    } else {
      Ket.out.println(" !!! Unexpected label() syntax !!! ");
      return false;
    }
    documentManager.addDocument(document);
    getKetPanel().updateAndRepaint();
    return true;
  }

  private String recognizeName() {
    // TODO: Check that the file was opened rather than only creating a blank document.
    Branch currentBranch = getCursor().asBranch();
    Argument first = currentBranch.getChild(0);
    return Like.getString(first);
  }

  private String recognizePath(int index) {
    Branch currentBranch = getCursor().asBranch();
    Argument first = currentBranch.getChild(index);
    return Like.getString(first);
  }

  private State getLocation() {
    Branch currentBranch = getCursor().asBranch();
    return currentBranch.getChild(2).getState();
  }

  /**
   * If current or one of its ancestor has the given function, move up to
   * the first instance and set it current, returning true on success.
   */
  private boolean moveUpToFunction(Function function) {
    if (getCurrent().getFunction()==function) {
      return true;
    }
    Vector<Branch> ancestors = getCurrent().getAncestors();
    boolean set = false;
    for (Branch parent : ancestors) {
      if (parent.getFunction()==function) {
        getSelection().setOnly(parent);
        set = true;
      }
    }
    return set;
  }

  /**
   * Expand powers (a+b)^c to (a+b)*(a+b)...(a+b).
   */
  public boolean expandPower() { // NOTE: This moves up while Transform.expandPower() does not!
    boolean ok = moveUpToFunction(Function.POWER);
    if ( ! ok ) {
      Ket.out.println(" !!! Can't expand powers of a non-power function !!! ");
      return false;
    }
    Argument current = Transform.expandPower(getCurrent());
    if (current==null) {
      return false;
    }
    getSelection().replace(current);
    return true;
  }

  /**
   * Multiply out a series of brackets or the squared sum of terms.
   */
  public boolean expandTimes() {
    // Can move up to times or power branch?
    Vector<Argument> ancestors = new Vector<Argument>(getCurrent().getAncestors());
    Collections.reverse(ancestors);
    ancestors.add(getCurrent());
    Argument multinomial = null;
    Argument powerOfProduct = null;
    Argument times = null;
    for (Argument a : ancestors) {
      // ASSUMPTION: prefer to expand (a*b)^n over expanding a*b.
      if (powerOfProduct!=a.getParent() && a.getFunction()==Function.TIMES) {
        times = a;
      } else if (Like.multinomialExpandable(a)) {
        multinomial = a;
      } else if (Like.intPowerOfProduct(a)) {
        powerOfProduct = a;
      }
    }
    if (multinomial!=null) { // (a+b...)^n -> ...+...+...
      setCurrent(multinomial);
      return multinomialExpand(); // (a+b)^n
      // Fully done?
    } else if (powerOfProduct!=null) { // (a*b*...)^n -> a^n * b^n * ...
      setCurrent(powerOfProduct);
      return powerOfProductExpand();
    } else if (times!=null) {
      setCurrent(times);
      return multiplyOut();
    }
    return false;
  }

  private boolean multiplyOut() {
    Argument current = Transform.multiplyOut(getCurrent());
    if (current==null) {
      return false;
    }
    getSelection().replace(current);
    return true;
  }

  private boolean powerOfProductExpand() {
    Argument current = Transform.powerOfProductExpand(getCurrent());
    if (current==null) {
      return false;
    }
    getSelection().replace(current);
    return true;
  }

  private boolean multinomialExpand() {
    Argument current = Transform.multinomialExpand(getCurrent());
    if (current==null) {
      return false;
    }
    getSelection().replace(current);
    return true;
  }

  /**
   * Drill down to the first non-product-type argument within current and
   * add tem to a map to the associated function.  This also handles
   * minus signs.
   */
  public boolean organizeTimesType() { // TODO: Fix for -ve cases.
    if (!Type.timesType(getCurrent()) && !Like.negativeLike(getCurrent())) { // limit to a*b or -x etc.
      return false;
    }
    IdentityHashMap<Argument, Function> map = new IdentityHashMap<Argument, Function>();
    boolean positive = organizeTimesTypeRecur(map, getCurrent(), null);
    Ket.out.println("positive = " + positive);
    Ket.out.println("map = " + map);

    Vector<Argument> times = new Vector<Argument>();
    Vector<Argument> fraction = new Vector<Argument>();

    for (Argument a : map.keySet()) {
      Function op = map.get(a);
      if (op==Function.TIMES) {
        times.add(a);
      } else {
        fraction.add(a);
      }
    }

    int tSize = times.size();
    int fSize = fraction.size();
    Argument firstTimes = tSize!=0 ? times.firstElement() : null;
    Argument firstFraction = fSize!=0 ? fraction.firstElement() : null;
    Argument replacement = null;
    switch (fSize) {
      case 0:
        if (tSize==0) { // 1/1 -> 1
          replacement = new Token(1);
        } else if (tSize==1) { // a/1 -> a
          replacement = firstTimes;
        } else if (tSize>=2) { // a*b*c*...
          replacement = new Branch(Function.TIMES, times);
        }
        break;

      case 1:
        if (tSize==0) { // 1/a
          replacement = new Branch(Function.FRACTION, new Token(1), firstFraction);
        } else if (tSize==1) { // a/b
          replacement = new Branch(Function.FRACTION, firstTimes, firstFraction);
        } else if (tSize>=2) { // a*b*c*d/z
          Branch t = new Branch(Function.TIMES, times);
          replacement = new Branch(Function.FRACTION, t, firstFraction);
        }
        break;

      default:
        if (tSize==0) { // 1/(a*b*c)
          Branch f = new Branch(Function.TIMES, fraction);
          replacement = new Branch(Function.FRACTION, new Token(1), f);
        } else if (tSize==1) {
          Branch f = new Branch(Function.TIMES, fraction);
          replacement = new Branch(Function.FRACTION, firstTimes, f);
        } else if (tSize>=2) {
          Branch t = new Branch(Function.TIMES, times);
          Branch f = new Branch(Function.TIMES, fraction);
          replacement = new Branch(Function.FRACTION, t, f);
        }
    }
   
    if (positive) {
      getSelection().replace(replacement);
    } else {
      Branch negative = new Branch(Function.MINUS, replacement);
      getSelection().replace(negative);
    }

    return true;
  }
 
  /**
   * Clean up times and fraction sub-branches recursively into their cannonical form.
   */
  private boolean organizeTimesTypeRecur(IdentityHashMap<Argument,Function> map, Argument a, Function path) {// returns sign information
    Function op = Type.getUnitaryFunction(a);
    if (Type.timesType(a)) { // recur
      int sign = 0;
      for (Argument child : a.asBranch().getChildren()) {
        Function comp = null;
        if (path==null) {
          comp = op;
        } else if (op==null || op==Function.MINUS) { // <--- Think about this more carefully.
          comp = path;
        } else {
          comp = Type.getComposition(path, op);
        }
        sign += organizeTimesTypeRecur(map, child, comp) ? 0 : 1;
      }
      return sign%2==0; // Odd number of minus signs are positive.
    } else if (Like.negativeLike(a)) { // change sign, recur
      Argument child = a.asBranch().firstChild();
      return ! organizeTimesTypeRecur(map, child, path);
    } else { // Update map and sign.
      State s = a.getState();
      if (s instanceof NumberValue && ((NumberValue) s).getDouble()==1.0) {
        // Skip all 1's.
        return true;
      }
      if (op==Function.MINUS) { // ignore minus sign.
        map.put(a, path);
      } else if (path==null) {
        map.put(a, op);
      } else {
        map.put(a, Type.getComposition(path, op));
      }
      return true;
    }
  }

  /**
   * Drill down to the first non-positive-type argument within current
   * and add them to the map along with their unitary function. 
   **/
  public boolean organizeAddType() {
    Argument organized = Transform.organizeAddType(getCurrent());
    if (organized==null) {
      return false;
    }
    getSelection().replace(organized);
    return true;
  }

  public boolean editCurrentArgument() {
    //- if (getCurrent().isText()) {
    if (getCurrent().isMixed()) {
      modes.getMessage().setMessage(getCurrent().getText());
      modes.setDocumentState(DocumentState.UPDATE_TEXT);
    } else {
      modes.getMessage().setMessage(getCurrent().toEditString()); //?
      modes.setDocumentState(DocumentState.UPDATE_REPLACE);
    }
    return true;
  }

  public boolean editCurrentPurpose() {
    modes.getMessage().setMessage(getCurrent().getPurpose().getName());
    DocumentState ds = getCurrent() instanceof Branch ? DocumentState.APPEND_PURPOSE : DocumentState.UPDATE_REPLACE;
    modes.setDocumentState(ds);
    return true;
  }

  public void slideLeft() { //! a*b^x -> a^x*b
    Argument current = getCurrent();
    Argument sibling = current.getOnlySibling(); // Generalize to zero/multiple siblings.
    Branch parentBranch = getCurrent().getParentBranch();
    Argument neighbour = parentBranch.getPreviousSibling();
    if (neighbour==null) return;
    sibling.remove();
    parentBranch.replace(sibling);
    neighbour.replace(parentBranch);
    parentBranch.prepend(neighbour);
    setCurrent(current);
  }

  public void slideRight() {
    Argument current = getCurrent();
    Argument sibling = current.getOnlySibling(); // Generalize to zero/multiple siblings.
    Branch parentBranch = getCurrent().getParentBranch();
    Argument neighbour = parentBranch.getNextSibling();
    if (neighbour==null) return;
    sibling.remove();
    parentBranch.replace(sibling);
    neighbour.replace(parentBranch);
    parentBranch.prepend(neighbour);
    setCurrent(current);
  }

  public boolean gatherLeft() {
    Branch grandparentBranch1 = getCursor().getGrandparentBranch();
    if (grandparentBranch1==null) {
      Ket.out.println(" !!! Can't gather left: current argument's grandparent is not a branch !!! ");
      return false;
    }
    for (Argument child : grandparentBranch1.getChildren()) {
      if (child instanceof Branch) {
        Branch childBranch = (Branch) child;
        if (childBranch.size()>2) {
          childBranch.moveFirstChildOut();
        }
      }
    }
    getSelection().transpose()
    getDeleteMode().deleteParent();
    return true; //~
  }

  public boolean gatherRight() {
    Branch grandparentBranch2 = getCursor().getGrandparentBranch();
    if (grandparentBranch2==null) {
      Ket.out.println(" !!! Can't gather right: current argument's grandparent is not a branch !!! ");
      return false;
    }
    for (Argument child : grandparentBranch2.getChildren()) {
      if (child instanceof Branch) {
        Branch childBranch = (Branch) child;
        if (childBranch.size()>2) {
          childBranch.moveLastChildOut();
        }
      }
    }
    getSelection().transpose()
    getDeleteMode().deleteParent();
    return true; //~
  }

  public boolean actInLeft() {
    Argument current = getCurrent();
    if (current.isRoot()) {
      if (mathCollection.isRangeSelectionSet()) {
        return false;
      }
      mathCollection.getCursorSelection().insertIntoPreviousEquation();
      return true;
    } else if (current.isFirstArgument()) {
      return getSelection().swapOut();
    } // otherwise...
    Branch pb = getCursor().getParentBranch();
    if (pb.size()>2) {
      // When moving into the previous sibling, don't affect those other siblings around it.
      Function pf = pb.getFunction();
      Argument previousSibling = current.getPreviousSibling();
      Branch ipb = getSelection().addIntermediaryParent(pf);
      getSelection().setOnly(current); // Go back to old current.
      ipb.prepend(previousSibling); // Move previous aunt to previous sibling.
    }
    getCursor().selectLeft(); // 'h'
    getCursor().selectInOrOutRight(); // 'o'
    boolean ok = getSelection().swapAncestors(); // 'tt' // Can this fail?
    if (ok) {
      getSelection().setOnly(current);
    }
    return true;
  }

  public boolean actInRight() {
    Argument current = getCurrent();
    if (current.isRoot()) {
      if (mathCollection.isRangeSelectionSet()) {
        return false;
      }
      mathCollection.getCursorSelection().insertIntoBelowEquation();
      return true;
    } else if (current.isLastArgument()) {
      return getSelection().swapOut();
    } // otherwise
    Branch parentBranch = getCursor().getParentBranch();
    if (parentBranch.size()>2) {
      // When moving into the next sibling, don't affect those other siblings around it.
      Function pf = parentBranch.getFunction();
      Argument nextSibling = current.getNextSibling();
      Branch ipb = getSelection().addIntermediaryParent(pf);
      getSelection().setOnly(current); // Go back to old current.
      ipb.append(nextSibling); // Move next aunt to next sibling.
    }
    getCursor().selectRight(); // 'l'
    getCursor().selectInOrOutLeft(); // 'i'
    getSelection().swapAncestors(); // 'tt' // Can this fail?
    Ket.out.println(" *** &c:" + current.getAddress());
    getSelection().setOnly(current);
    return true;
  }

  public boolean startOrStopRecordingMacro(Chord chord) {
    Macros macros = getDocument().getKeyboardEventHandler().getMacros();
    if ( ! macros.isRecordingMacro()) {
      // Record a macro.
      chord.setComplete(false);
      modes.setDocumentState(DocumentState.RECORD_MACRO);
    } else {
      // Stop recording the macro.
      macros.stopRecordingMacro();
    }
    return false;
  }

  public boolean replaceSelectionWithIdentity() {
    Ket.out.printf("%60s--- replace selection with identity --- \n", "");
    Argument current = getCursor().getCurrent();
    int index = current.indexIn(referenceMatches);
    if (index==-1) {
      // New search
      Ket.out.println(" --- new search --- ");
      KnownArguments knownArguments = mathCollection.getKnownArguments();
      referenceMatches = findIdentity(current, knownArguments);
      if (referenceMatches.size()==0) {
        Ket.out.println(" !!! No identities in the references match the current selection !!! ");
        return false;
      }
      Ket.out.println("matches:");
      for (Argument match : referenceMatches) {
        Ket.out.println("\t" + match);
      }
      Argument substitute = referenceMatches.firstElement();
      getSelection().replace(substitute);
      // Current is now identically equal to the first element
      // of referenceMatches.
    } else {
      Ket.out.println("[cycle through existing matches "+referenceMatches.size()+"]");
      // Cycle through the various matches.
      int nextIndex = (index + 1) % referenceMatches.size();
      Argument substitute = referenceMatches.get(nextIndex);
      getSelection().replace(substitute);
    }
    return true;
  }

  static Vector<Argument> identities = null;

  private static Vector<Argument> findIdentity(Argument target, KnownArguments knownArguments) {
    Ket.out.println(" === find identity === ");
    Ket.out.println("\ttarget = " + target);
    if (identities==null) {
      loadIdentity(knownArguments);
    }
    Vector<Argument> matches = new Vector<Argument>();
    for (Argument potentialIdentity : identities) {
      Argument substitute = ArgumentTools.identityLike(target, potentialIdentity);
      if (substitute!=null) {
        Ket.out.println("\tid:" + potentialIdentity);
        Ket.out.println("\tsub:" + substitute);
        matches.add(substitute);
      }
    }
    return matches;
  }

  private static void loadIdentity(KnownArguments knownArguments) { // TODO: Move this to a suitable class. // Static method?
    Ket.out.printf("%sLOAD IDENTITIES\n", "");
    identities = new Vector<Argument>();
    for (String filename : FILES) {
      MathCollection mathCollection = new MathCollection(knownArguments); // NOTE: knownArguments isn't used but does avoid null pointer exceptions: remove code weakness and remove replace with null.
      boolean ok = mathCollection.readJar(filename);
      if (!ok) {
        Ket.out.println(" !!! Can't read from '" + filename + "' to search for identities !!! ");
        continue;
      }
      Ket.out.println("Loading '" + filename + "'");
      Vector<Equation> equations = mathCollection.getEquationList().getEquations();
      for (Equation e : equations) {
        Argument root = e.getRoot();
        // if (root.getFunction()==Function.EQUALS) {
        Argument[] pair = Like.equalPairLike(root);
        if (pair!=null) { // ?=?
          identities.add(root);
        }
      }
    }
  }

  public boolean replacePrefixNotation(Chord chord) {
    getClipboard().setDeletedArgument(getCurrent());
    modes.setDocumentState(DocumentState.UPDATE_PREFIX);
    chord.setComplete(false);
    return true;
  }

  public boolean replaceCurrentArgument(Chord chord) {
    getClipboard().setDeletedArgument(getCurrent());
    modes.setDocumentState(DocumentState.UPDATE_REPLACE);
    chord.setComplete(false);
    return true;
  }

  public void halfSelection() {
    NumberValue numberValue = getCurrent().getNumberValue();
    if (numberValue==null) {
      getSelection().replace(new Token(1));
    } else {
      State decreasedState = numberValue.half();
      getCurrent().asToken().setState(decreasedState);
    }
  }

  public void twiceSelection() {
    NumberValue numberValue = getCurrent().getNumberValue();
    if (numberValue==null) {
      getSelection().replace(new Token(1));
    } else {
      State decreasedState = numberValue.twice();
      getCurrent().asToken().setState(decreasedState);
    }
  }

  public void incrementSelection() {
    NumberValue numberValue = getCurrent().getNumberValue();
    if (numberValue==null) {
      getSelection().replace(new Token(1));
    } else {
      State decreasedState = numberValue.increase();
      getCurrent().asToken().setState(decreasedState);
    }
  }

  public void decrementSelection() {
    NumberValue numberValue = getCurrent().getNumberValue();
    if (numberValue==null) {
      getSelection().replace(new Token(-1));
    } else {
      State decreasedState = numberValue.decrease();
      getCurrent().asToken().setState(decreasedState);
    }
  }

  public boolean zeroSelection() {
    getSelection().replace(new Token(0));
    return true;
  }

  public boolean cyclicallyPermuteLeft() {
    if (getCurrentParent() instanceof Branch) {
      ((Branch) getCurrentParent()).cyclicallyPermuteLeft();
      return true;
    } else {
      modes.error(" !!! Can't cyclically permute (left) the order of a non-branch !!! ");
      return false;
    }
  }

  public boolean cyclicallyPermuteRight() {
    if (getCurrentParent() instanceof Branch) {
      ((Branch) getCurrentParent()).cyclicallyPermuteRight();
      return true;
    } else {
      modes.error(" !!! Can't cyclically permute (right) the order of a non-branch !!! ");
      return false;
    }
  }

  public boolean substitutePurpose(Chord chord) {
    if (mathCollection.isRangeSelectionSet()) {
      modes.getAddMode().appendPurpose(chord);
      return true;
    }
    getClipboard().setDeletedArgument(getCurrent());
    chord.setComplete(false);
    if (getCurrent() instanceof Branch) {
      modes.setDocumentState(DocumentState.APPEND_PURPOSE);
      return true;
    } else if (getCurrent().isText()) {
      modes.setDocumentState(DocumentState.UPDATE_TEXT);
      return true;
    } else {
      // Simple version.
      modes.setDocumentState(DocumentState.UPDATE_REPLACE); // Replace rather than append the state.
      return true;
    }
  }

  /**
   * Return true if a change was made.
   */
  public boolean toggleSubtraction() {
    // TODO: Extend to multiple selections.
    // TODO: Extend range so move up to ADD or MINUS or PM or MP?
    Branch currentBranch = getCursor().asBranch();
    if (Like.longSubtractionLike(currentBranch)) {
      getSelection().replace(Transform.contractSubtraction(currentBranch));
      return true;
    } else if (Like.shortSubtractionLike(currentBranch)) {
      getSelection().replace(Transform.expandSubtraction(currentBranch));
      getSelection().flatten();
      return true;
    } else {
      return false;
    }
  }

  public boolean saveAndExit() {
    // TODO: Ensure that any differences are saved.
    Ket.out.println();
    String name = getDocument().getFrameManager().getRawFilename();
    if (name!=null) {
      getDocument().getFrameManager().dispose();
    } else {
      Ket.out.println(" !!! No filename !!! ");
      modes.error("No filename.");
    }
    return false;
  }

  class WindowOpener implements Runnable {
    public WindowOpener() {

    }
    public void run() {
      DocumentManager documentManager = getDocumentManager();
      Document d = new Document(documentManager, null, null);
      documentManager.addDocument(d);
    }
  }

  public boolean openNewWindow() {
    if (getDocument().getFrameManager().isFullScreen()) return false;
    if (getDocumentManager()==null) return false;
    SwingUtilities.invokeLater(new WindowOpener());
    return true;
  }

  public void showAlbum() {
    new Album(getDocument(), cycle);
  }

  public void cycleFamily(boolean forward) {
    cycle.cycleFamily(forward, getSelection());
  }

  public void cycleRelation(boolean forward) {
    cycle.cycleRelation(forward, getSelection());
  }

  public void DEBUG() {
    for (int i=0; i<10; i++) {
      Ket.out.println();
    }
  }

  public boolean selectNextUnknown(boolean searchBackwards) {
    int settings = ArgumentVector.INCLUDE_ROOT;
    if (searchBackwards) {
      settings |= ArgumentVector.REVERSE_ITERATOR_ORDER;
    }
    ArgumentVector av = new ArgumentVector(getCurrent().getVisibleRoot(), settings);
    boolean seek = true;
    for (Argument a : av) {
      if (seek) { // i.e. Skip until after current.
        if (a==getCurrent()) {
          seek = false;
        }
        continue;
      } // Afterwards:
      //- Purpose purpose = a.getPurpose();
      //- if (purpose==Function.UNKNOWN || Symbol.UNKNOWN==purpose) {
      if (Like.isUnknown(a)) {
        getSelection().setCurrent(a);
        return true;
      }
    }
    for (Argument a : av) {
      if (a==getCurrent()) break; // i.e. Search until current.
      //- Purpose purpose = a.getPurpose();
      //- if (purpose==Function.UNKNOWN || Symbol.UNKNOWN==purpose) {
      if (Like.isUnknown(a)) {
        getSelection().setCurrent(a);
        return true;
      }
    }
    return false;
  }

  public boolean moveToNephew() {
    Branch parentBranch = getCurrent().getParentBranch();
    if (parentBranch==null) return false;
    Argument sibling = getCurrent().getPreviousSibling();
    if (sibling==null || sibling instanceof Token) return false;
    Argument nephew = sibling.asBranch().firstChild();
    if (nephew==null) return false;
    mathCollection.setCursorSelection();
    mathCollection.getDrag().moveToNephew(parentBranch, nephew, mathCollection.getKnownArguments());
    return true;
  }

  public boolean invertInequality() {
    Ket.out.println(" --- Invert inequality --- ");
    Vector<Argument> roots = getSelection().getSubbranchRoots();
    if (roots.size()>1) {
      Ket.out.println("[multiple]");
      boolean change = false;
      EquationList el = getSelection().getEquationList();
      for (Argument a : roots) { // WARNING: Some roots may be affected by previous edits
        if (a.getEquationList()!=el) {
          continue;
        }
        getSelection().setOnly(a);
        change |= invertInequality();
      }
      return change;
    }
    Ket.out.println("[next "+getCurrent()+"]");
    Vector<Branch> ancestors = getCurrent().getAncestors();
    Ket.out.println("ancestors: "+ancestors);
    if (ancestors.size()>0) {
      Function f = ancestors.firstElement().getFunction();
      Ket.out.println("f=" + f);
      if (f==Function.ADD || f==Function.TIMES || f==Function.MINUS || f==Function.FRACTION) { // Too specific: use Function.*Type().
        Ket.out.println("[relative]");
        invertRelative(ancestors);
        return true;
      }
    }
    Argument parentBranch = getCurrent().getParentBranch();
    Ket.out.println("parent branch="+ parentBranch);
    if (parentBranch==null) {
      Ket.out.println(" !!! invert inequality :: null parent branch !!! ");
      return false;
    }
    Argument end = getCurrent().getParentBranch().getOnlySibling();
    if (end==null) {
      Ket.out.println("[null end]");
      Argument otherEnd = getCurrent().getOnlySibling();
      if (otherEnd==null || !otherEnd.isBranch()) return false;
      Function otherFunction = otherEnd.getFunction();
      if (Type.addType(otherFunction) || Type.timesType(otherFunction)) { // a+b=x -> a+b=x+0 -> (a+b)-x=0
        Branch positive = getCurrent().addIntermediaryParent(Type.getPositiveCase(otherFunction));
        positive.append(Type.getNullToken(otherFunction));
        invertRelative(getCurrent().getAncestors());
        return true; //?
      } else {
        Argument child = otherEnd.asBranch().firstChild();
        otherEnd = getCurrent();
        getCursor().setOnly(child);
        return mathCollection.getDrag().invertEquality(otherEnd);
      }
    } else {
      Ket.out.println("end=" + end);
      if (! getCurrent().hasUncle(end)) return false;
      mathCollection.setCursorSelection();
      return mathCollection.getDrag().invertEquality(end);
    }
  }

  /**
   * Handle +, -, * and / inversion as a special case.
   */
  private void invertRelative(Vector<Branch> ancestors) {
    Branch common = null;
    Argument old = getCurrent();
    Argument end = null;
    for (Branch ancestor : ancestors) {
      // TODO: Generalize to include all inequalities (INEQUALITY_TYPE).
      //- Argument[] args = Like.equalPairLike(ancestor);
      Argument[] args = Like.equalityPairLike(ancestor);
      if ( args != null ) {
        common = ancestor;
        if (args[0]==old) {
          end = args[1];
          break;
        } else if (args[1]==old) {
          end = args[0];
          break;
        }
      }
      old = ancestor;
    }
    if (end!=null) {
      mathCollection.getDrag().relatedAlgebra(common, end);
    }
  }

  public boolean togglePurpose() { // 'Q'
    KnownArguments knownArguments = mathCollection.getKnownArguments();
    return getSelection().togglePurpose(knownArguments);
  }

  public boolean swapBetweenTokenAndBranch() {
    KnownArguments knownArguments = mathCollection.getKnownArguments();
    getSelection().togglePurpose(knownArguments);
    return true;
  }

  public boolean map() {
    Ket.out.println(" --- Map --- ");
    Branch branch = getCurrent().asBranch();
    Branch parentBranch = getCurrent().getParentBranch();
    if (parentBranch==null || branch==null) return false;
    Function function = branch.getFunction();
    Ket.out.println("\tfunction = " + function);
    for (Argument c : parentBranch.getChildren()) {
      if (c==branch) continue;
      setCurrent(c);
      Branch result = getSelection().addIntermediaryParent(function);
    }
    setCurrent(branch);
    Ket.out.println("\tparent = " + parentBranch);
    return true;
  }

  public boolean substituteUpdate(Chord chord) {
    getClipboard().setDeletedArgument(getCursor().getRoot());
    modes.setDocumentState(DocumentState.UPDATE_SUBSTITUTE);
    chord.setComplete(false);
    return true;
  }

  /**
   * Convert a given integer into its power of ten multiple by a list of
   * powers of its factors.
   */
  public boolean factorizeIntegers() { 
    Integer value = Like.getInteger(getCurrent());
    if (value==null) {
      Ket.out.println(" !!! Cannot factorize a non-integer !!! ");
      return false;
    }
    Argument factorizedInteger = Factorize.factorize(value.intValue(), false);
    if (factorizedInteger==null) {
      Ket.out.println(" !!! Can't factorize "+value+" !!! ");
      return false;
    }
    getSelection().getCurrent().replace(factorizedInteger);
    setCurrent(factorizedInteger);
    return true;
  }


  public void equateEvaluate() { // Convert 3*2 into 3*2 -> 6
    Argument a = getCurrent();
    Argument clone = a.cloneArgument();
    Branch to = a.addIntermediaryParent(Function.TO);
    to.append(clone);
    Selection s = getSelection();
    ArgumentVector av = new ArgumentVector(clone, ArgumentVector.INCLUDE_ROOT);
    //?+ Collections.reverse(av);
    for (Argument next : av.toBranchVector()) { // 3/4 -> 0.75
      Argument frac = Transform.integerDivideFraction(next);
      if (frac==null) {
        frac = Transform.divideFraction(next);
      }
      if (frac!=null) {
        s.replace(next, frac);
      }
    }
    s.setCurrent(to.lastChild());
    getSelection().evaluate();
  }

  public long pow10(int exponent) {
    long p = 1L;
    for (int i=0; i<exponent; i++) {
      p *= 10;
    }
    return p;
  }

  public void setPrecision(Chord chord) {
    if (chord.countChanged()) {
      chord.setLoopSkipped(true);
    }
    Double value = Like.getNumber(getCurrent());
    if (value==null) return;
    int counts = chord.getCounts();
    if (counts<0) return;
    long p = pow10(counts);
    value = Math.floor(value * p);
    value /= p;
    getSelection().replace(new Token(value));
  }

  public boolean replaceAllMatches() {
    Ket.out.println(" --- replace all matches --- ");
    boolean changed = false;
    Argument before = getCurrent();
    EquationList el = getCursor().getEquationList();
    for (Argument a : new ArgumentVector(getCurrent(), ArgumentVector.INCLUDE_ROOT)) {
      Ket.out.println("next = " + a);
      if (a.getState() instanceof NumberValue) continue;
      Ket.out.println("[non-number]");
      if (a.getEquationList()!=el) continue;
      Ket.out.println("[not removed]");
      changed |= replaceWithMatches(a);
      Ket.out.println("result: " + before);
    }
    if (before.getEquationList()==el) {
      getSelection().setCurrent(before);
    }
    return changed;
  }

  public boolean replaceWithMatches(Argument target) {
    getSelection().setCurrent(target);
    EquationList el = getCursor().getEquationList();
    Vector<Argument> matches = new Vector<Argument>();
    //- for (Equation e : el.getEquations()) { // Search all other equations for "<target>=<anything>".
    for (int i=0; i<getCursor().getEquationIndex(); i++) { // Search all previous equations.
      Equation e = el.getEquation(i);
      if (e==target.getEquation()) continue;
      Argument root = e.getVisibleRoot();
      Vector<Argument> args = new ArgumentVector(root, ArgumentVector.INCLUDE_ROOT);
      for (Argument assignment : args) {
        Argument q = Like.assignmentLike(target, assignment);
        if (q!=null) {
          matches.add(q.cloneArgument());
        }
      }
    }
    if (matches.isEmpty()) return false;
    // if current equals
    Ket.out.println(" --- replace with matches ("+target+") --- ");
    Ket.out.println("matches = " + matches);
    Ket.out.println("current = " + getCurrent());
    for (int i=matches.size()-1; i>=0; i--) { // Remove duplicates.
      Argument m = matches.get(i);
      for (int j=0; j<i; j++) {
        Argument n = matches.get(j);
        if (m.equals(n)) {
          matches.remove(i);
          break;
        }
      }
    }
    Branch currentParent = getCurrent().getParentBranch();
    Ket.out.println("matches' = " + matches);
    if (currentParent!=null && Type.assignType(currentParent)) {
      // Avoid recursively substituting the previous copy of the current equation.
      Ket.out.println("current parent = " + currentParent);
      for (int i=matches.size()-1; i>=0; i--) { // Look through current subject's siblings and exclude matches that it is equated to.
        Argument m = matches.get(i);
        Ket.out.println("(match "+i+") = "+m);
        for (Argument child : currentParent.getChildren()) {
          Ket.out.println("\tchild:" + child);
          if (m.equals(child)) {
            Ket.out.println("\t\t[EQUALS]");
            matches.remove(i);
            break;
          }
        }
      }
      Ket.out.println("matches'' = " + matches);
    }
    switch (matches.size()) { // TODO: Simplify
      case 0:
        return false;
        /*-
      case 1:
        getSelection().replace(matches.firstElement());
        return true;
        */
      default:
        //- getSelection().replace(new Branch(Function.VECTOR, matches));
        getSelection().replace(matches.lastElement());
        return true;
    }
  }

  // --------
  public boolean variablesToList() {
    Ket.out.println("[perturb as flat]");
    Branch root = getCursor().getRoot().asBranch();
    if (root==null) return false;
    if (Like.hasForm(root, Function.TO, 2)) { // f(x)+y^z -> f x plus power y z
      Ket.out.println("[Contract]");
      Branch source = root.firstChild().asBranch();
      Branch destination = root.lastChild().asBranch();
      source.remove();
      getSelection().setCurrent(root);
      getSelection().deleteIntermediate();
      if (destination==null || source==null) return false;
      //- Vector<Argument> src = new ArgumentVector(source, 0);
      Vector<Argument> src = source.getChildren();
      Vector<Argument> dest = new ArgumentVector(destination, 0);
      for (int i=0,j=0; i<src.size() && j<dest.size(); j++) {
        Argument s = src.get(i);
        Argument d = dest.get(j);
        if (d.isBranch() && d.size()>=1) {
          // ?
        } else {
          d.replace(s);
          i++;
        }
      }
    } else {
      Ket.out.println("[Expand]");
      getSelection().setCurrent(root);
      Branch to = getSelection().addIntermediaryParent(Function.TO);
      Branch flat = new Branch(Function.COMMA);
      to.prepend(flat);
      Vector<Argument> av = new ArgumentVector(root, 0); //- ArgumentVector.INCLUDE_ROOT
      for (Argument next : av) {
        Argument clone;
        if (next.isBranch() && next.size()>=1) {
          //- clone = new Token(new Word(next.getFunction().getName()));
          continue;
        } else {
          clone = Argument.cloneArgument(next);
        }
        flat.append(clone);
      }
      getSelection().setCurrent(flat.firstChild());
    }
    return true;
  }
  // --------
////////////////////////////////////////////////////////////////////////////////
////////////////////////// SHORTCUT ACCESSOR METHODS ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

  private void setOnly(Argument current) {
    getSelection().setOnly(current);
  }

  public Argument getCurrent() {
    return getCursor().getCurrent();
  }

  private void setCurrent(Argument current) {
    getCursor().setCurrent(current);
  }

  private Cursor getCursor() {
    return mathCollection.getCursor();
  }

  private Parent getCurrentParent() {
    return getCurrent().getParent();
  }

  public Document getDocument() {
    return modes.getDocument();
  }

  public KetPanel getKetPanel() {
    return modes.getDocument().getKetPanel();
  }

  public EquationList getEquationList() {
    return getSelection().getEquationList();
  }

  private Selection getSelection() {
    return mathCollection.getSelection();
  }

  private Clipboard getClipboard() {
    return   modes.getClipboard();
  }

  private DocumentManager getDocumentManager() {
    return modes.getDocumentManager();
  }

  private DeleteMode getDeleteMode() {
    return modes.getDeleteMode();
  }
}
TOP

Related Classes of ketUI.modes.NormalMode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.