Copyright (C) 2000, 2001, Geert Vernaeve. All Rights Reserved.
package gve.calc.formula; import java.awt.*; public class IdentifierView extends CursorPosLabel implements KeydnListener,View,MouseSensitive { private Identifier model; public IdentifierView(Identifier model) { super(model.getString()); this.model = model; MVC.registerView(model,this); } public IdentifierView(int pos,Identifier model) { super(model.getString(),pos); this.model = model; MVC.registerView(model,this); }
Model has changed. Synchronize displayed text with model.
public void updateView(Object arg) { setText(model.getString()); invalidate(); // mark me for resize } public Dimension getPreferredSize() { if (getText().equals("") && getCursorPos()<0) { FontMetrics fm = getFontMetrics(getFont()); return new Dimension(fm.charWidth(' '),fm.getHeight()); } else return super.getPreferredSize(); } public void paint(Graphics g) { if (getText().equals("") && getCursorPos()<0) { FontMetrics fm = getFontMetrics(getFont()); g.setColor(Color.red); g.fillRect(0,0,fm.charWidth(' '),fm.getHeight()); } else super.paint(g); } /* public void deactivate() { super.deactivate(); // cursor off if (model.parent instanceof OperatorSpace) { OperatorSpace par = (OperatorSpace)model.parent; // E.g. if the user typed "a_subs_b" (where _ is a space), // then completed to "a_subseteq_b", the recognizeOp() makes // that as soon as the cursor leaves the "subseteq", the operator // is recognized. // Note: we cannot recognize operators when we are (e.g. temporarily // in the course of a formula tree manipulation) unlinked from the // FormulaView component tree! (then view==null) // We also don't recognize operators when we are part of a replace operation // (we would call recognizeOp() and so indirectly do another replace // recursively, etc). FormulaView view = FormulaView.get(this); if (view == null) return; if (view.isReplacing()) return; if (par.recognizeOp(view.getFormula(),view)) return; // operator recognized } }*/ public Object getModel() { return model; } public boolean mousePressed(int flags,int x,int y) { return false; } // almost same as StaticStringView.mouseDown public void mouseDown(int flags,int x,int y) { FontMetrics fm = getFontMetrics(getFont()); String string = model.getString(); int len = string.length(); FormulaView view = FormulaView.get(this); for (int i = 0; i < len; i++) { int w = fm.charWidth(string.charAt(i)); if (x < w/2) { view.setCursorComp(this,i); return; } x -= w; // advance one letter to the right } // Place cursor at the rightmost position view.setCursorComp(this,len); }
Note that the way this method handles KEY_LEFT and KEY_RIGHT is totally unsuitable for subclasses with children. This is because when a child cannot move the cursor farther to (say) the left, it asks its parent to do so. We don't detect this situation, since it cannot occur in childless classes.
public boolean keydn(int key) { int cp = getCursorPos(); //System.out.println("IdentifierView key "+key+" pos "+cp); switch (key) { case Key_LEFT: if (cp > 0) { setCursorPos(cp - 1); //System.out.println("Put the cursor at "+getCursorPos()+" size "+getSize()); repaint(); return true; } break; case Key_RIGHT: if (cp < getText().length()) { setCursorPos(cp + 1); repaint(); return true; } break; case ')': case '}': case ']': return false; case '"': if (getText().equals("")) { FormulaView view = FormulaView.get(this); StringPart sp = new StringPart(); view.getFormula().replace(model,sp); view.setCursorPart(sp,1); return true; } break; case '(': case '{': case '[': case '`': if (getText().equals("")) { Brackets br = new Brackets((char)key); FormulaView view = FormulaView.get(this); view.getFormula().replace(model,br); view.setCursorPart(br.child); return true; } else if (cp == getText().length()) { Brackets br = new Brackets((char)key); Identifier dummy = new Identifier(""); FormulaView view = FormulaView.get(this); Formula f = view.getFormula(); // if we keep the cursor at this Identifier, the next replace() // will move the cursorComp out of the formula tree! // when the setCursorPart() moves the cursor away, // it would call this.deactivate(), but since we are unlinked from // the formula tree, we would not be able to find our FormulaView ... view.setCursorComp(null); f.replace(model,dummy); OperatorSpace spat; f.replace(dummy,spat = new OperatorSpace(model,br)); // PENDING: this is no longer true // We don't need to call spat.recognizeOp(f) // because moving the cursor will call this.deactivate() // which will call recognizeOp() by itself ... (25-may-2000 bugfix) view.setCursorPart(br.child); spat.recognizeOp(f,view); return true; } break; case Key_BS: if (cp == 0) { // Nothing to delete; try to merge FormulaView view = FormulaView.get(this); String string = model.getString(); if (model.parent instanceof InfixBinaryOp) { InfixBinaryOp par = (InfixBinaryOp)model.parent; if (model == par.right) { // Inspired on InfixBinaryOp.dismantle() String parname = par.getName(); parname = parname.substring(0,parname.length()-1); if (parname.equals("") && par.left instanceof Identifier) { // e.g. when you have x+[] and push backspace, result should be x[] String leftstr = ((Identifier)par.left).getString(); Identifier ident = new Identifier(leftstr + string); view.getFormula().replace(par,ident); view.setCursorPart(ident,leftstr.length()); return true; } else if (parname.equals("") && string.equals("")) { // to test: (x/y)+[] --bspace--> (x/y)[] view.getFormula().replace(par,par.left); view.setCursorPart(par.left,Somewhere_RIGHT); return true; } else { // to test: a += []b --bspace--> a +[]b // to test: (a) + []b --baspace--> (a) []b Identifier ident = new Identifier(parname + string); OperatorSpace spat; view.getFormula().replace(par,spat = new OperatorSpace(par.left, ident)); view.setCursorPart(ident,parname.length()); // In the first example, we get "+[]b" as an identifier ... ((IdentifierView)view.getView(ident)).splitme(); return true; } } } else if (model.parent instanceof PrefixUnaryOp) { PrefixUnaryOp par = (PrefixUnaryOp)model.parent; String parname = par.getName(); parname = parname.substring(0,parname.length()-1); Identifier ident = new Identifier(parname+string); view.getFormula().replace(par,ident); view.setCursorPart(ident,parname.length()); return true; } if (model.parent instanceof OperatorSpace) { OperatorSpace spat = (OperatorSpace)model.parent; if (model==spat.right && spat.left instanceof Identifier) { Identifier l = (Identifier)spat.left; Identifier id = new Identifier(l.getString()+string); int cpos = l.getString().length(); view.getFormula().replace(spat,id); view.setCursorPart(id,cpos); return true; } if (model==spat.left && spat.parent instanceof OperatorSpace) { OperatorSpace spatpar = (OperatorSpace)spat.parent; if (spatpar.left instanceof Identifier) { Identifier l = (Identifier)spatpar.left; Identifier id = new Identifier(l.getString()+string); int cpos = l.getString().length(); OperatorSpace repl = new OperatorSpace(id,spat.right); view.getFormula().replace(spatpar,repl); view.setCursorPart(id,cpos); return true; } } } // PENDING return false; // Propagate upwards } else { String txt = getText(); txt = txt.substring(0,cp-1) + txt.substring(cp); model.setString(txt); setCursorPos(cp - 1); tryRecognizeOp(); } return true; case Key_DEL: { String txt = getText(); if (cp == txt.length()) { // UNIMPLEMENTED return true; } txt = txt.substring(0,cp) + txt.substring(cp+1); model.setString(txt); } tryRecognizeOp(); return true; default: if (key < 32) return false; { String txt = getText(); txt = new String(new StringBuffer(txt).insert(cp,(char)key)); setCursorPos(cp + 1); model.setString(txt); // splitme(); } if (tryRecognizeOp()) return true; /* if (model.parent instanceof OperatorSpace) { OperatorSpace par = (OperatorSpace)model.parent; // E.g. if the user typed "a_subs_b" (where _ is a space), // then completed to "a_subseteq_b", the recognizeOp() makes // that as soon as the cursor leaves the "subseteq", the operator // is recognized. FormulaView view = FormulaView.get(this); if (par.recognizeOp(view.getFormula(),view)) return true; // operator recognized }*/ splitme(); return true; } return false; }
Try to recognize an operator, e.g. when the string field has been modified
boolean tryRecognizeOp() { if (model.parent instanceof OperatorSpace) { OperatorSpace par = (OperatorSpace)model.parent; // E.g. if the user typed "a_subs_b" (where _ is a space), // then completed to "a_subseteq_b", the recognizeOp() makes // that as soon as the cursor leaves the "subseteq", the operator // is recognized. FormulaView view = FormulaView.get(this); if (par.recognizeOp(view.getFormula(),view)) return true; // operator recognized } return false; }
If this is not an identifier any more, take appropriate action.
void splitme() { String string = getText(); if (string.equals("")) return; FormulaView view = FormulaView.get(this); //view.printdebug(); if (string.equals(" ")) { // This can happen when the user types "x". // This causes split() to create a space operator with as // children Identifier("x") and Identifier(" "). if (view.getCursorComp()==this && getCursorPos()>0) setCursorPos(0); model.setString(""); return; } Formula f = view.getFormula(); { // Prefix operator? String pref = PrefixUnaryOp.operatorPrefix(string); if (pref!=null && pref.equals(string)) { // Try to recognize operator (10-May-2001) Part par = model.getParent(); if (par instanceof OperatorSpace) { OperatorSpace spat = (OperatorSpace)par; if (spat.recognizeOp(f,view)) return; } } else if (pref!=null && (!pref.equals(string) /* || f.getCursorPart()!=this */ )) { // PENDING int preflen = pref.length(); if (Character.isLetter(string.charAt(preflen-1)) && Character.isLetter(string.charAt(preflen))) ; // don't split in middle of word else { Identifier ident = new Identifier(string.substring(preflen)); int movecursorto = -1; if (view.getCursorComp()==this && getCursorPos() > preflen) movecursorto = getCursorPos() - preflen; OperatorSpace spat = new OperatorSpace(new Identifier(pref),ident); f.replace(model,spat); if (movecursorto >= 0) view.setCursorPart(ident,movecursorto); spat.rotate(f,view); //System.out.println("voor");spat.dump(); //System.out.println("parent "); if (spat.parent != null) spat.parent.dump(); spat.recognizeOp(f,view); //System.out.println("na"); f.getRootPart().dump(); // UnaryOp recognized; // f.replace(model,recognized = PrefixUnaryOp.getInstance(pref,ident)); // Rotate??? (Pending!) // recognized.rotate(f,view); if (f.contains(ident)) ((IdentifierView)view.getView(ident)).splitme(); return; } } } // Infix operator? // PENDING: this algorithm looks fluffy. Recheck. for (int i = 0; i < string.length(); i++) { String pref = InfixBinaryOp.operatorPrefix(string.substring(i)); if (pref == null) continue; // If the user wants to type "+=", we should not recognize an "+" // operator as soon as the first letter is typed if (pref.equals(string)) // will be recognized later, don't split // if cursor just left this part, do split it. // if (f.getCursorPart() == this) // PENDING return; //System.out.println("Split "+string+" "+pref+" at "+i); // Found operator name somewhere in the string. Split. if (i == 0) { // Operator is on the left if (pref.equals(" ")) {// special case for space // When the user types 'forallP' and then places a space before the P, // 'forall' is first split into an operator with chid " P". We discard the // extra space here. model.setString(string = string.substring(1)); if (view.getCursorComp()==this && getCursorPos()>0) setCursorPos(getCursorPos()-1); splitme(); return; } // e.g. user typed "a+b", this was split into "a" and "+b" // so now we have to split "+b" into "+" and "b" int preflen = pref.length(); if (Character.isLetter(string.charAt(preflen-1)) && Character.isLetter(string.charAt(preflen))) continue; // don't split in middle of word Identifier right = new Identifier(string.substring(preflen)); OperatorSpace spat; spat = new OperatorSpace(new Identifier(pref),right); boolean movecursor = false; int moveto = 0; // x+[] --y-> x[+]y ---> x+y[] if (view.getCursorComp()==this && string.length()==getCursorPos()) { movecursor = true; moveto = HasCursorPos.Somewhere_RIGHT; } else if (view.getCursorComp()==this && getCursorPos()>=preflen) // union[]y --space-> union []y --this split-> (union) and ([] y) --split right part-> (union) and (y) movecursor = true; moveto = getCursorPos() - preflen; f.replace(model,spat); if (movecursor) view.setCursorPart(right,moveto); spat.rotate(f,view); Part spatr = spat.right; // save this before recognizeOp() mutilates spat spat.recognizeOp(f,view); // PENDING: -> spatView.recognizeOp() of zoiets if (f.contains(spatr)) { // See if we can create an operator out of the rest of this identifier // e.g. when you type "anot x" and change it into "a,not x" Part spatrpar = spatr.getParent(); //System.out.println("parent "+spatrpar); //spatrpar.dump(); if (spatrpar instanceof OperatorSpace) ((OperatorSpace)spatrpar).recognizeOp(f,view); //f.getRootPart().dump(); } if (f.contains(spatr) && spatr instanceof OperatorSpace) // can happen when modifying "forall []forall y:a" into "forall x:forall y:a" ((OperatorSpace)spatr).recognizeOp(f,view); if (f.contains(right)) // It's possible that spatrpar.recognizeOp() sweeped us out of existence ((IdentifierView)view.getView(right)).splitme(); // recurse into rest of identifier return; } else { if (Character.isLetter(string.charAt(i-1)) && Character.isLetter(string.charAt(i))) continue; // don't split in middle of word Identifier right; OperatorSpace spat; if (pref.equals(" ")) // special case for space right = new Identifier(string.substring(i+1)); else right = new Identifier(string.substring(i)); spat = new OperatorSpace(new Identifier(string.substring(0,i)),right); // x[] ---> x[]+ ---> x+[] waarbij [] de cursor voorstelt int cp = -1; if (view.getCursorComp() == this) cp = getCursorPos(); // 22-aug-00: replaced 'boolean movecursor' // by 'int cp' (PENDING: do the same in the case i==0 above???) f.replace(model,spat); //System.out.println("cp="+cp+" i="+i); if (cp >= 0) { if (cp < i) view.setCursorPart(spat.left,cp); else { // special case e.g. when "sin" -> "sin " if (pref.equals(" ")) view.setCursorPart(right,cp-i-1); else view.setCursorPart(right,cp-i); } } spat.rotate(f,view); if (!spat.recognizeOp(f,view)) { // PENDING: -> spatView.recognizeOp() of zoiets // Only continue the splitting if we didn't recognize an operator ((IdentifierView)view.getView(right)).splitme(); } return; } } // Postfix operator? for (int i = 0; i < string.length(); i++) { String pref = PostfixUnaryOp.operatorPrefix(string.substring(i)); if (pref == null) continue; int cpos = -1; if (view.getCursorComp() == this) cpos = getCursorPos(); UnaryOp op = PostfixUnaryOp.getInstance(pref,new Identifier(string.substring(0,i))); if (i+pref.length() < string.length()) { // aaaPbbb -> aaaP space bbb Identifier right = new Identifier(string.substring(i+pref.length())); OperatorSpace spat = new OperatorSpace(op,right); f.replace(model,spat); op.rotate(f); spat.recognizeOp(f,view); ((IdentifierView)view.getView(right)).splitme(); } else { // aaaP -> aaa P f.replace(model,op); op.rotate(f); if (cpos < 0) ; // cursor was not here else if (cpos < i) { // cursor was inside aaa // PENDING } else { // cursor was inside P if (cpos-i < pref.length()/2) // cursor was at left side of P view.setCursorPart(op,HasCursorPos.Somewhere_LEFT); else view.setCursorPart(op,HasCursorPos.Somewhere_RIGHT); } } return; } }
Try to create an Empty part at cursor position Returns that Empty part, or null
public Identifier createEmptyAtCursor() { FormulaView view = FormulaView.get(this); if (view.getCursorComp() != this) return null; if (model.isEmpty()) return model; if (getCursorPos() == model.getString().length()) { // insert at right Identifier dummy = new Identifier(""); Formula f = view.getFormula(); f.replace(model,dummy); Identifier result = new Identifier(""); OperatorSpace spat = new OperatorSpace(new Identifier(model.getString()),result); f.replace(dummy,spat); spat.recognizeOp(f,view); return result; } return null; } }