Copyright (C) 2000, 2001, Geert Vernaeve. All Rights Reserved.
package gve.calc.formula; import awt.*; import awt.Label; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.Hashtable; import java.util.Enumeration; import java.util.Vector;
This class manages a Component tree, which is the visual representation of a Formula. It receives input events from the user, and adjusts the Formula accordingly. Note that this is not only a View in the pure sense of the word, but a wrapper around a tree of Views. These views recieve "I'm modified in this and this" messages from the formula (Part objects) and adjust the Component tree accordingly.
public class FormulaView extends Container implements View,HasBaseline { private Formula formula; private boolean replacing = false; private Component cursorComp;
key: part, value: component
private Hashtable components = new Hashtable(); public FormulaView(Formula f) { formula = f; setLayout(null); // we do the layout ourselves, not by a layout manager add(getView(f.getRootPart())); MVC.registerView(formula,this); }
If the given part has already made a view, return it from the internal hashtable. Else, create the view using p.createView() and store it in the hashtable.
public Component getView(Part p) { { Object obj = components.get(p); if (obj != null) { if (obj instanceof Component) return (Component)obj; throw new Error("this cannot happen."); } } Part part = p; if (part.getParent()==null) if (part != formula.getRootPart()) throw new Error("part "+part+" is not in formula tree"); Component comp = p.createView(this); components.put(p,comp); return comp; } public int getBaseline() { Component rootView = getView(formula.getRootPart()); if (rootView instanceof HasBaseline) { return ((HasBaseline)rootView).getBaseline(); } return getSize().height / 2; } public Component viewAt(int x,int y) { return getView(partAt(x,y)); } public Part partAt(int x,int y) { Part p = formula.getRootPart(); while (true) { View view = (View)getView(p); Part child; for (int i = 0; ; i++) { child = p.getChild(i); //System.out.println("Checking "+p+" for pixel "+x+","+y); if (child == null) break; // no more children Component childView = getView(child); Point loc = childView.getLocation(); //System.out.println("\t checking "+child); if (childView.contains(x-loc.x,y-loc.y)) { // Descend to this child x -= loc.x; y -= loc.y; break; } } if (child != null) { // Descend to this child p = child; continue; } return p; } }
Return relative position of comp (which is supposed to be a (sub(sub...))child of this FormulaView) to this FormulaView.
public Point getLocationInFormula(Component comp) { Point loc = comp.getLocationOnScreen(); Point here = getLocationOnScreen(); return new Point(loc.x - here.x,loc.y - here.y); }
If a Component returns true on mousePressed(), it handles mouse drags all by itself. Else, the system will draw a selection rectangle.
public boolean mousePressed(int flags,int x,int y) { Component view = viewAt(x,y); //System.out.println("mouse pressed "+view); if (view instanceof MouseSensitive) { Point loc = getLocationInFormula(view); return ((MouseSensitive)view).mousePressed(flags,x-loc.x,y-loc.y); } return false; } public void mouseDown(int flags,int x,int y) { Part bestPart = partAt(x,y); //System.out.println("mouse down in "+bestPart); Component view = getView(bestPart); if ((flags & Part.Mouse_RIGHT) != 0) { // menu button Window w = createPopup(); for (Part p = bestPart; p != null; p = p.parent) { // Create menus for this part and all its parents Component comp = getView(p); Component pop = null; Point pos = getLocationInFormula(comp); if (comp instanceof CreatesPopup) pop = ((CreatesPopup)comp).createPopup(x-pos.x,y-pos.y); String name; if (p instanceof Operator) { name = ((Operator)p).getName(); if (name.equals(" ")) name = "[space]"; } else { name = p.getClass().getName(); int i = name.lastIndexOf('.'); if (i >= 0) name = name.substring(i+1); } if (p==bestPart) { // Menu of 'this' part is always shown directly w.add(new Label(name,Label.CENTER)); if (pop != null) w.add(pop); } else { // Menu's of parent parts are accessible via submenu's if (pop != null) { MenuButton mb = new MenuButton(name,comp,MenuButton.RIGHT); w.add(mb); mb.add(pop); mb.pack(); } else w.add(new Label(name,Label.CENTER)); } } //System.out.println("About to pack window "+w); w.pack(); Point pos = getLocationOnScreen(); // netscape4.7winnt bug: press right button => show menu // release button -> most of the cases, mouse hasn't moved and is above the new menu // => menu gets this mouse event and disappears! // Hence show menu at y+1 coordinate (and not at y) w.setLocation(pos.x+x-w.getSize().width/2,pos.y+y+1); w.setVisible(true); //System.out.println("Shown "+w); } else if (view instanceof MouseSensitive) { // left mouse button Point loc = getLocationInFormula(view); ((MouseSensitive)view).mouseDown(flags,x-loc.x,y-loc.y); } } public Window createPopup() { awt.MenuButton.closepopups(); Window w = awt.MenuButton.createPopup(this); w.setLayout(new awt.GridPackLayout(0,1)); return w; }
Is this formula view busy replace()ing? Used by former versions of Identifier.deactivate() (they tried to recognizeOp() but only when isReplacing() returns false in order to avoid recursion situations). When this method returns true, deactivate() or activate() should not touch the formula tree structure (i.e. don't add or replace elements).
public boolean isReplacing() { return replacing; }
This method is very slow if the view is not implementing View.
public Part getModel(Component view) { if (view instanceof View) return (Part)((View)view).getModel(); Enumeration elements = components.keys(); while (true) { Part model; try { model = (Part)elements.nextElement(); } catch (java.util.NoSuchElementException exc) { break; } if (getView(model) == view) return model; } return null; } public Formula getFormula() { return formula; } public void cursorOff() { setCursorComp(null); } public void cursorOn() { setCursorComp(getComponent(0),HasCursorPos.Somewhere); } public Component getCursorComp() { return cursorComp; } public Part getCursorPart() { return getModel(cursorComp); } public void setCursorComp(Component c,int pos) { if (cursorComp==c) { if (cursorComp instanceof HasCursorPos) ((HasCursorPos)cursorComp).setCursorPos(pos); return; } if (cursorComp!=null && cursorComp instanceof ActivationListener) ((ActivationListener)cursorComp).deactivate(); cursorComp = c; if (c!=null && c instanceof ActivationListener) ((ActivationListener)c).activate(); if (cursorComp!=null && cursorComp instanceof HasCursorPos) ((HasCursorPos)cursorComp).setCursorPos(pos); } public void setCursorComp(Component c) { setCursorComp(c,HasCursorPos.Somewhere); } public void setCursorPart(Part p,int pos) { setCursorComp(getView(p),pos); } public void setCursorPart(Part p) { setCursorComp(getView(p)); } public void doLayout() { //System.out.println("layout formula "+this); Component view = getComponent(0); view.validate(); Dimension dim = view.getPreferredSize(); view.setBounds(0,0,dim.width,dim.height); //System.out.println("layouted formula "+this); } public Dimension getPreferredSize() { return getComponent(0).getPreferredSize(); } public Dimension getMinimumSize() { return getComponent(0).getMinimumSize(); } public void updateView(Object with) { if (with instanceof ReplacedMessage) { ReplacedMessage msg = (ReplacedMessage)with; replace(getView(msg.old),getView(msg.byThat)); } else throw new Error("FormulaView doesn't understands message "+with); }
Component to be replaced (me) must be in the Component tree.
// PENDING: if 'me' has no view and 'byThat' has no view, do nothing private void replace(Component me,Component byThat) { replacing = true; Container parent = me.getParent(); Component oldCursor = cursorComp; // If we would let the cursor live, the following would happen: // we remove me from the FormulaView component tree, // then call deactivate() by moving the cursor, so deactivate() // cannot find its FormulaView! if (cursorComp == me) setCursorComp(null); if (Part.debuglevel >= 30) { System.out.println("Replace "+parent); System.out.println(parent.getComponentCount()); } int count; for (count = parent.getComponentCount()-1; count>=0; count--) { if (parent.getComponent(count) == me) { parent.remove(count); parent.add(byThat,count); break; } } if (gve.calc.Main.debuglevel > 10) { if (count < 0) { // did not find component!!! System.out.println("*******FormulaView: could not replace*******"); } } if (oldCursor == me) setCursorComp(byThat,HasCursorPos.Somewhere); // validate(); // resize if necessary replacing = false; if (Part.debuglevel >= 95) { System.out.println("REPLACED "+me+" BY "+byThat); list(); System.out.println("--------------END OF REPLACE DUMP------------"); } } // this method just made for StackCanvas ... public Object clone() { return new FormulaView((Formula)formula.clone()); }
return true if something happened return false if we cannot handle the key press.
public boolean keydn(int key) { //System.out.println("FormulaView: keydn "+key+" -> "+cursorComp); Component tryComp = cursorComp; while (tryComp != null) { //System.out.println("\t-> "+tryComp); if (tryComp instanceof KeydnListener) if (((KeydnListener)tryComp).keydn(key)) return true; // If this node cannot handle the key, ask its parent. Part model = getModel(tryComp); if (model.parent == null) break; tryComp = getView(model.parent); } if (Part.debuglevel>=25) System.out.println("\t-> key not processed"); return false; }
Components in the tree can use this method to find their containing FormulaView.
public static FormulaView get(Component comp) { while (comp != null) { if (comp instanceof FormulaView) return (FormulaView)comp; comp = comp.getParent(); } return null; } public Object getModel() { return formula; }
Look if p is inside the area, then recursively look in all of its children
private void addPartsInside(Part p,Vector parts,Rectangle area) { if (p instanceof Uneditable) return; // Uneditables cannot be selected Component pview = getView(p); Rectangle r; { Point loc = getLocationInFormula(pview); Dimension siz = pview.getSize(); r = new Rectangle(loc,siz); // p occupies this rectangle } Rectangle doorsnede = r.intersection(area); // width and height may be < 0 so width*height is not really the surface area ... if (doorsnede.isEmpty()) return; // If more than 70% of the part is selected, take it if (doorsnede.width*doorsnede.height*10 >= 7*r.width*r.height) parts.addElement(p); // Recurse for (int i = 0; ; i++) { Part child = p.getChild(i); if (child == null) return; addPartsInside(child,parts,area); } }
Return a vector of Parts that are inside the designated rectangle
public Vector getPartsInside(Rectangle area) { Vector result = new Vector(); addPartsInside(formula.getRootPart(),result,area); return result; } // Some utility methods
Insert keypress to the left or right of this Part Here, special keys will be recognized and acted upon accordingly, e.g. typing '(' inserts a new Brackets part, etc
public final boolean splitkey(Part p,int key,boolean attheleft) { if (key < 32) return false; Identifier dummy = new Identifier(""); Formula f = getFormula(); f.replace(p,dummy); Identifier typed; if (key == ' ') typed = new Identifier(""); else typed = new Identifier(""+(char)key); f.replace(dummy,attheleft ? new OperatorSpace(typed,p) : new OperatorSpace(p,typed)); setCursorPart(typed,HasCursorPos.Somewhere_RIGHT); return true; }
Insert a keypress to the left of this Part
public final boolean splitleft(Part p,int key) { return splitkey(p,key,true); }
Insert a keypress to the right of this Part
public final boolean splitright(Part p,int key) { return splitkey(p,key,false); } public VariableStore getVariableStore() { Component comp = this; while (comp != null) { if (comp instanceof VariableStore) return (VariableStore)comp; comp = comp.getParent(); } return null; } public Part getVar(String varname) { return getVariableStore().getVar(varname); } public void storeVar(String varname,Part content) { getVariableStore().storeVar(varname,content); } public Part load(String filename) { return getVariableStore().load(filename); } public void addUndoAction(UndoAction action) { Component par = getParent(); if (par instanceof UndoListener) ((UndoListener)par).addUndoAction(action); else throw new Error("cannot undo"); } public void printdebug() { getFormula().printdebug(); System.out.print("Cursor at:"); getCursorPart().dump(); if (cursorComp instanceof HasCursorPos) { System.out.println("Cursor pos: "+((HasCursorPos)cursorComp).getCursorPos()); } } }