Copyright (C) 2000, 2001, Geert Vernaeve. All Rights Reserved.
package gve.calc; import awt.*; import awt.Button; import java.awt.*; import java.awt.FileDialog; import java.awt.event.*; import java.util.Vector; import java.util.Hashtable; import java.io.*; import gve.calc.formula.*; import gve.calc.formula.Boolean; import gve.calc.graph.*; import gve.calc.logic.*; import gve.calc.packaging.*; /* Draws visible portion of a formula stack inside a Canvas. * We extend Panel and not Component, so this is a heavyweight component, * since that is the only way to get rid of double buffering flicker. * stack[0] is labeled "n:" * stack[1] "n-1:" * ... * stack[n-2] "2:" * stack[n-1] "1:" * where n == vector.size() */ public class StackCanvas extends Panel implements MouseListener,MouseMotionListener,KeyListener,VariableStore,UndoListener,FocusListener { private int totalHeight, maxWidth; private StackWindow win; /* activeFormula: * inside [0 .. formulas.size()-1] if the cursor is inside a formula * -1 for no active formula */ private int activeFormula; private int xtop,ytop,totalheight; private Scrollbar scroller,horscroller; private Evaluator variables = new MultiEvaluator(); private UndoAction undoAction; private FormulaView highlightFormula; private Part highlightPart; // selected part, ready to Cut/Copy private static Part clipboard; public static final Color highlightColor = new Color(128,128,192); public StackCanvas(Scrollbar vs,Scrollbar hs,StackWindow w) { scroller = vs; horscroller = hs; win = w; setLayout(null); totalHeight = maxWidth = 0; activeFormula = -1; ytop = 0; setBackground(Color.white); setForeground(Color.black); addMouseListener(this); addKeyListener(this); // Strange: nothing comes in (Linux, jdk1.1.6) // but needed in netscape // w.addKeyListener(this); // More strange: nothing comes in addFocusListener(this); StandardFunctions.registerFunctions(variables); } //////// package manager
key: string (e.g. "gve.calc.formula.Matrix"), value: corresponding PartDescriptor object
private Hashtable partsTable = new Hashtable(); public void importpkg(String pkgname) { PackageDescriptor pkg = PackageDescriptor.get(pkgname); PartDescriptor [] parts = pkg.getParts(); if (parts == null) return; for (int i = parts.length-1; i>=0; i--) { // import parts[i] String fullname = pkgname+"."+parts[i].name; win.addInsertItem(parts[i].name,fullname); partsTable.put(fullname,parts[i]); } } public void newObject(String classname) { Identifier empty = createEmptyAtCursor(); if (empty == null) return; FormulaView view = (FormulaView)getComponent(activeFormula); PartDescriptor part = (PartDescriptor)partsTable.get(classname); part.generateNew(empty,view); requestFocus(); repaint(); } public void setActiveFormula(int i) { if (i == activeFormula) return; if (activeFormula >= 0) { FormulaView f = (FormulaView)getComponent(activeFormula); f.cursorOff(); repaint(); } activeFormula = i; if (activeFormula >= 0) { FormulaView f = (FormulaView)getComponent(activeFormula); f.setCursorComp(f.getComponent(0)); } } public void setActiveFormula(Component comp) { for (int i = getComponentCount()-1; i>=0; i--) if (getComponent(i) == comp) { setActiveFormula(i); return; } setActiveFormula(-1); } public void moveX(int x) { xtop = x; invalidate(); repaint(); horscroller.setValue(x); } public void moveY(int y) { ytop = y; invalidate(); repaint(); scroller.setValue(y); } public FormulaView push(Formula f) { FormulaView result = new FormulaView(f); push(result); return result; } public void push(FormulaView f) { add(f); repaint(); } public void dup() { int stacksize = getComponentCount(); if (stacksize == 0) return; // stack empty! add( (FormulaView)((FormulaView)getComponent(stacksize-1)).clone()); addUndoAction(null); } public void dup2() { int stacksize = getComponentCount(); // Copy formulas add( (FormulaView)((FormulaView)getComponent(stacksize-2)).clone()); add( (FormulaView)((FormulaView)getComponent(stacksize-1)).clone()); addUndoAction(null); } public void addUndoAction(UndoAction action) { undoAction = action; win.undoButton.setEnabled(action != null); } public void undo() { if (undoAction == null) new SimpleDialog(this,"Belgium, man!","Cannot undo","Dismiss",false).getResult(); else { undoAction.undo(); undoAction = null; } win.undoButton.setEnabled(false); } public void adjustMenus() { win.pasteButton.setEnabled(clipboard != null); } public void cut() { if (highlightPart == null) return; clipboard = highlightPart; Identifier hole = new Identifier(""); // After cutting, all that remains is a hole Part cursor = highlightFormula.getCursorPart(); boolean cursormove = false; if (cursor != null) { // When the cursor is inside the cut, move it if (Part.isDescendantOr(cursor,highlightPart)) { cursormove = true; highlightFormula.cursorOff(); // will reappear soon } } highlightFormula.getFormula().replace(highlightPart,hole); if (cursormove) highlightFormula.setCursorPart(hole); win.adjustAllMenus(); setHighlight(null,null); } public void copy() { if (highlightPart == null) return; clipboard = (Part)highlightPart.clone(); win.adjustAllMenus(); setHighlight(null,null); } public void paste() { if (clipboard == null) return; Identifier empty = createEmptyAtCursor(); if (empty == null) return; Formula f = ((FormulaView)getComponent(activeFormula)).getFormula(); f.replace(empty,(Part)clipboard.clone()); } public void drop() { int size = getComponentCount(); if (size == 0) return; // cannot drop (PENDING: some error) SimpleDialog dialog; dialog = new SimpleDialog(this,"Sure to drop?",true); dialog.addText("Drop formula 1?"); dialog.addButton("OK, erase it"); dialog.addButton("Oops, keep it"); String result = dialog.getResult(); if (result.equals("OK, erase it")) { addUndoAction(new DropUndo(this,(FormulaView)getComponent(size-1))); remove(size-1); } } public FormulaView pop() { FormulaView res; int size = getComponentCount(); if (activeFormula == size-1) setActiveFormula(-1); if (size == 0) return null; res = (FormulaView)getComponent(size-1); addUndoAction(new DropUndo(this,res)); remove(size-1); return res; }
Note: It's not a good idea to reposition children inside paint(), so we do it here. This is because calling setBounds() on a visible lightweight container *always* causes a repaint() to happen, whether the setBounds() actually modified the size and position or not.
public void doLayout() { //long start=System.currentTimeMillis(); if (Part.debuglevel >= 10) System.out.println("Layouting ..."); // if (Part.debuglevel >= 96) try { throw new Error("Layouting"); } catch (Error err) { err.printStackTrace(System.out); } int ypos = -ytop; int width = getSize().width; int number = getComponentCount(); FontMetrics metrics = getGraphics().getFontMetrics(); int numberWidth = metrics.stringWidth(number + ":"); for (int count = 0; count < number; count++) { Component child = getComponent(count); child.validate(); Dimension thisDim = child.getPreferredSize(); FormulaView f = (FormulaView)child; int xpos = (width-numberWidth-thisDim.width) / 2; if (xpos < 0) xpos = 0; f.setBounds(-xtop+numberWidth+xpos,ypos,thisDim.width,thisDim.height); if (Part.debuglevel >= 20) System.out.println(count+": @"+xpos+","+ypos+" dim "+thisDim); if (Part.debuglevel >= 90) f.list(); ypos += thisDim.height + 1; } if (Part.debuglevel >= 10) System.out.println("Layouting done"); repaint(); // Formula number labels may have moved //long time=System.currentTimeMillis()-start; //System.out.println("StackCanvas.doLayout(): spent "+time); } private Image offscreen; public void paint(Graphics onscreen) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); //long start=System.currentTimeMillis(); //System.out.println("paint canvas "+onscreen.getClipBounds()); if (!isValid()) { //System.out.println("validating"); validate(); // auto-layout //System.out.println("Validated"); } int ypos = -ytop; int width = getSize().width; int number = getComponentCount(); FontMetrics metrics = onscreen.getFontMetrics(); int fontAscent = metrics.getAscent(); int fontHeight = metrics.getHeight(); int numberWidth = metrics.stringWidth(number + ":"); Dimension siz = getSize(); if (offscreen==null || offscreen.getWidth(null)!=siz.width || offscreen.getHeight(null)!=siz.height) { if (offscreen != null) offscreen.flush(); offscreen = createImage(siz.width,siz.height); } Graphics g = offscreen.getGraphics(); Rectangle clip = onscreen.getClipBounds(); g.setClip(clip.x,clip.y,clip.width,clip.height); g.setColor(Color.white); //g.setColor(new Color((int)(Math.random()*256),(int)(Math.random()*256),(int)(Math.random()*256))); g.fillRect(0,0,siz.width,siz.height); g.setColor(highlightColor); if (highlightFormula!=null && highlightPart!=null) { Component view = highlightFormula.getView(highlightPart); Dimension psiz = view.getSize(); Point loc = view.getLocationOnScreen(); Point meLoc = getLocationOnScreen(); g.fillRect(loc.x-meLoc.x,loc.y-meLoc.y,psiz.width,psiz.height); } g.setColor(Color.black); maxWidth = 0; //System.out.println("StackCanvas.paint(): preparations "+(System.currentTimeMillis()-start)); super.paint(g); //System.out.println("StackCanvas.paint(): super "+(System.currentTimeMillis()-start)); for (int count = 0; count < getComponentCount(); count++,number--) { Dimension thisDim = getComponent(count).getPreferredSize();//(Dimension)dimensions.elementAt(count); if (thisDim.width > maxWidth) maxWidth = thisDim.width; g.drawString(number + ":", -xtop,fontAscent + ypos + (thisDim.height - fontHeight) / 2); ypos += thisDim.height + 1; } //System.out.println("StackCanvas.paint(): loop "+(System.currentTimeMillis()-start)); g.setColor(Color.black); showRubberBand(g); g = onscreen; g.drawImage(offscreen,0,0,siz.width,siz.height,0,0,siz.width,siz.height,this); totalHeight = ypos + ytop - 1; // Adjust scrollbar // value,visible,min,max scroller.setValues(scroller.getValue(),siz.height,0,totalHeight); scroller.setBlockIncrement((siz.height*4)/5); horscroller.setValues(horscroller.getValue(),siz.width,0,numberWidth + maxWidth); horscroller.setBlockIncrement((siz.width*4)/5); //System.out.println("at "+ytop+"; visible "+siz.height+"; max "+(totalHeight-siz.height)); // display Y-coord range is [0..siz.height[ // painted Y-coord range is [-ytop..ypos[ if (ypos<siz.height && ytop>0) { // We have room to scroll down! int newytop = ytop - (siz.height-ypos); // make sure we don't scroll too far // i.e. we don't create empty space at the top if (newytop < 0) newytop = 0; moveY(newytop); } setCursor(Cursor.getDefaultCursor()); //long time=System.currentTimeMillis()-start; //System.out.println("StackCanvas.paint(): spent "+time); } public void update(Graphics g) { paint(g); }
Show part p in formula no i
public void showPart(Part p,int i) { if (p == null) { showFormula(i); return; } FormulaView fview = (FormulaView)getComponent(i); Component pview = fview.getView(p); Point formulaLoc = fview.getLocationInFormula(pview); // add (xtop,ytop) to convert from screen coordinates // to coordinate system where (0,0) is top pixel of the stack int xmin = fview.getLocation().x + formulaLoc.x + xtop; int ymin = fview.getLocation().y + formulaLoc.y + ytop; Dimension psiz = pview.getSize(); showArea(xmin,xmin + psiz.height,ymin,ymin + psiz.height); }
i is the *internal* number of the formula (0..getComponentCount()-1)
public void showFormula(int i) { Rectangle bounds = getComponent(i).getBounds(); showArea(bounds.x,bounds.x + bounds.width,bounds.y,bounds.y + bounds.height); }
Try to display the area with Y values [ymin..ymax] as good as we can. 0 is the top of the stack (the 'oldest' element). Coordinates: (0,0) is top left of the stack (not of the visible portion on screen).
public void showArea(int xmin,int xmax,int ymin,int ymax) { // Check Y ranges and adjust if necessary { // Currently shown area is [ytop..ytop+getSize().height] int shownYmin = ytop, shownYmax = ytop+getSize().height; if (shownYmin<=ymin && shownYmax>=ymax) return; //okay // We cannot show 'excess' of the pixel rows int excess = ymax-ymin - getSize().height; if (excess > 0) { // cannot show fully, so do our best moveY(ymin + excess/2); } else if (shownYmin > ymin) { // scroll down moveY(ymin); } else if (shownYmax < ymax) { // scroll up moveY(ymax - getSize().height); } } // idem for X ranges { // Currently shown area is [xtop..xtop+getSize().width] int shownXmin = xtop, shownXmax = xtop+getSize().width; if (shownXmin<=xmin && shownXmax>=xmax) return; //okay // We cannot show 'excess' of the pixel columns int excess = xmax-xmin - getSize().width; if (excess > 0) { // cannot show fully, so do our best moveX(xmin + excess/2); } else if (shownXmin > xmin) { // scroll down moveX(xmin); } else if (shownXmax < xmax) { // scroll up moveX(xmax - getSize().width); } } }
User resized window. Readjust scrollbars.
public synchronized void setBounds(int x,int y,int width,int height) { super.setBounds(x,y,width,height); if (width<=0 || height <=0) return; // value,visible,min,max if (totalHeight < height) { if (ytop != 0) // everything is visible so we cannot scroll // any more. Make sure that all is shown. moveY(0); } scroller.setValues(ytop,height,0,totalHeight - height); } private Identifier createEmptyAtCursor() { if (activeFormula == -1) { // Cursor is inactive add(new FormulaView(new Formula())); setActiveFormula(getComponentCount() - 1); } FormulaView view = (FormulaView)getComponent(activeFormula); Component cursor = view.getCursorComp(); if (cursor instanceof IdentifierView) { Identifier ident = (Identifier)((View)cursor).getModel(); if (ident.isEmpty()) return ident; return ((IdentifierView)cursor).createEmptyAtCursor(); } return null; } public void newGraph() { Identifier empty = createEmptyAtCursor(); if (empty == null) return; Formula f = ((FormulaView)getComponent(activeFormula)).getFormula(); Graph graph = new Graph(); f.replace(empty,graph); repaint(); }
Return id of formula at this position on the screen, or -1 if no formula there
public int formulaAt(int x,int y) { int ypos = -ytop; Graphics g = getGraphics(); int width = getSize().width; FontMetrics metrics = g.getFontMetrics(); int numberWidth = metrics.stringWidth(getComponentCount() + ":"); for (int count = 0; count < getComponentCount(); count++) { Dimension thisDim = getComponent(count).getPreferredSize(); if (ypos<=y && y<=ypos+thisDim.height) return count; ypos += thisDim.height + 1; } return -1; } public void focusGained(FocusEvent evt) {} public void focusLost(FocusEvent evt) { setActiveFormula(-1); }
Start point of mouse drag motion
private int mouseDragX,mouseDragY;
Position of currently drawn rubber band: (mouseDragX,mouseDragY) x (rubberX,rubberY) If mousePrevX < 0, no band is currently drawn. NOTE: These are screen pixels; (0,0) is the top left pixel of the Canvas, whatever the position of the scrollbars might be.
private int rubberX,rubberY; // click without move: pressed ... released,clicked // click and drag: press ... release public void mouseReleased(MouseEvent evt) { removeMouseMotionListener(this); requestFocus(); int taxidist = Math.abs(mouseDragX-rubberX) + Math.abs(mouseDragY-rubberY); eraseRubberBand(getGraphics()); if (taxidist<5) mouseClicked(evt); } public void mouseEntered(MouseEvent evt) {} public void mouseExited(MouseEvent evt) {} public void mousePressed(MouseEvent evt) { //System.out.println("StackCanvas mousepress"); awt.MenuButton.closepopups(); int x = evt.getX() + xtop; int y = evt.getY(); int flags = 0; // Which part is pressed? if (evt.isMetaDown()) flags |= Part.Mouse_RIGHT; Component comp = getComponentAt(evt.getX(),evt.getY()); FormulaView view = null; if (comp instanceof FormulaView) view = (FormulaView)comp; if (view != null) { Point loc = view.getLocation(); if (view.mousePressed(flags,evt.getX()-loc.x,evt.getY()-loc.y)) // p will handle mouse drags itself return; } // click outside all formulas // Prepare to drag a selection rubberband box mouseDragX = evt.getX(); mouseDragY = evt.getY(); rubberX = -1; addMouseMotionListener(this); setHighlight(null,null); } public void mouseClicked(MouseEvent evt) { //System.out.println("Stackcanvas Mouse Click"); int x = evt.getX() + xtop; int y = evt.getY(); int flags = 0; // if (evt.isAltDown()) System.out.println("alt"); // left button if (evt.isMetaDown()) flags |= Part.Mouse_RIGHT; // if (evt.isShiftDown()) System.out.println("shift"); // if (evt.isControlDown()) System.out.println("ctrl"); // if (evt.isPopupTrigger()) flags |= Part.Mouse_RIGHT; Component comp = getComponentAt(evt.getX(),evt.getY()); setActiveFormula(comp); FormulaView view = null; if (comp instanceof FormulaView) view = (FormulaView)comp; if (view != null) { Point loc = view.getLocation(); view.mouseDown(flags,evt.getX()-loc.x,evt.getY()-loc.y); return; } // click outside all formulas setActiveFormula(-1); } public void mouseDragged(MouseEvent evt) { Graphics g = getGraphics(); eraseRubberBand(g); rubberX = evt.getX(); rubberY = evt.getY(); int no = formulaAt(mouseDragX+xtop,mouseDragY+ytop); if (no >= 0) { Point loc = getComponent(no).getLocation(); // top left corner of this formula int xmin,xmax,ymin,ymax; if (mouseDragX < rubberX) { xmin = mouseDragX; xmax = rubberX; } else { xmin = rubberX; xmax = mouseDragX; } if (mouseDragY < rubberY) { ymin = mouseDragY; ymax = rubberY; } else { ymin = rubberY; ymax = mouseDragY; } FormulaView f = (FormulaView)getComponent(no); Vector parts = f.getPartsInside( new Rectangle(xmin-loc.x,ymin-loc.y,xmax-xmin,ymax-ymin)); // g.setColor(Color.red); // for (int i = parts.size()-1; i>=0; i--) { // Rectangle r = f.getPosition((Part)parts.elementAt(i)); // if (r != null) g.drawRect(loc.x+r.x,loc.y+r.y,r.width,r.height); // } // g.setColor(Color.black); setHighlight(f,f.getFormula().commonParent(parts)); } else setHighlight(null,null); showRubberBand(g); }
Make a part of a formula the highlighted part
public void setHighlight(FormulaView f,Part p) { if (highlightPart==p && highlightFormula==f) return; if (highlightPart!=null && highlightFormula!=null) { Component view = highlightFormula.getView(highlightPart); view.setBackground(null); } highlightPart = p; highlightFormula = f; repaint(); win.cutButton.setEnabled(p != null); win.copyButton.setEnabled(p != null); if (f!=null && p!=null) { Component view = f.getView(p); view.setBackground(highlightColor); } } public void mouseMoved(MouseEvent evt) {}
If shown, erase the rubber band from the screen
private void eraseRubberBand(Graphics g) { if (rubberX < 0) return; // no band visible drawRubberBand(g,mouseDragX,mouseDragY,rubberX,rubberY); rubberX = -1; } private void showRubberBand(Graphics g) { if (rubberX < 0) return; // no band to draw drawRubberBand(g,mouseDragX,mouseDragY,rubberX,rubberY); }
Utility function
private void drawRubberBand(Graphics g,int x1,int y1,int x2,int y2) { g.setColor(Color.black); g.setXORMode(Color.white); int xmin,ymin,width,height; if (x1<x2) { xmin = x1; width = x2-x1; } else {xmin = x2; width = x1-x2; } if (y1 < y2) { ymin = y1; height = y2-y1; } else {ymin = y2; height = y1-y2; } g.drawRect(xmin,ymin,width,height); g.setPaintMode(); } // Keypress/release -> label van een toets; dus '(' geeft geen keypress; '6' wel, 'shift' ook public void keyPressed(KeyEvent evt) { try {switch (evt.getKeyCode()) { case KeyEvent.VK_UP: keydn(KeydnListener.Key_UP); break; case KeyEvent.VK_DOWN: keydn(KeydnListener.Key_DOWN); break; case KeyEvent.VK_LEFT: keydn(KeydnListener.Key_LEFT); break; case KeyEvent.VK_RIGHT: keydn(KeydnListener.Key_RIGHT); break; case KeyEvent.VK_ENTER: keydn('\n'); break; case KeyEvent.VK_F1: if (activeFormula >= 0) { System.out.println("Dump of formula "+activeFormula); FormulaView view = (FormulaView)getComponent(activeFormula); view.getFormula().printdebug(); } break; case KeyEvent.VK_F2: if (activeFormula >= 0) { System.out.println("Dump of formula "+activeFormula); FormulaView view = (FormulaView)getComponent(activeFormula); view.list(); } break; case KeyEvent.VK_F3: try { System.out.println(System.getProperty("java.version")); } catch (SecurityException exc) {} break; // default: System.out.println("keypress "+evt.getKeyChar()); break; }} catch (Exception exc) { exc.printStackTrace(); System.out.print("Active formula is "+activeFormula); } if (!isValid()) repaint(); // auto-layout } public void keyReleased(KeyEvent evt) {} public void keyTyped(KeyEvent evt) { if (evt.getKeyChar() == '\n') return; // keyPressed does this try { int key = evt.getKeyChar(); if (key==KeydnListener.Key_TAB && (evt.getModifiers()&evt.SHIFT_MASK)!=0) key = KeydnListener.Key_SHTAB; keydn(key); } catch (Exception exc) { exc.printStackTrace(); System.out.print("Active formula is "+activeFormula); } if (!isValid()) validate(); // auto-layout } /* Pop two operands from the stack, join using the operator, * and push back */ private void doInfixBinOp(String opName) { if (getComponentCount() < 2) return; Part rightarg = pop().getFormula().getRootPart(); Part leftarg = pop().getFormula().getRootPart(); int rootpri = InfixBinaryOp.getInstance(opName,new Identifier(""),new Identifier("")).getPri(); if (leftarg instanceof Operator && ((Operator)leftarg).getPri() < rootpri) leftarg = new Brackets(leftarg); if (rightarg instanceof Operator && ((Operator)rightarg).getPri() < rootpri) rightarg = new Brackets(rightarg); InfixBinaryOp newroot = InfixBinaryOp.getInstance(opName,leftarg,rightarg); Formula f = new Formula(newroot); push(f); } private void doPostfixUnOp(String opName) { if (getComponentCount() < 1) return; Part arg = pop().getFormula().getRootPart(); int rootpri = PostfixUnaryOp.getInstance(opName,new Identifier("")).getPri(); if (arg instanceof Operator && ((Operator)arg).getPri() < rootpri) arg = new Brackets(arg); Formula f = new Formula(PostfixUnaryOp.getInstance(opName,arg)); push(f); } private void doPrefixUnOp(String opName) { if (getComponentCount() < 1) return; Part arg = pop().getFormula().getRootPart(); int rootpri = PrefixUnaryOp.getInstance(opName,new Identifier("")).getPri(); if (arg instanceof Operator && ((Operator)arg).getPri() < rootpri) arg = new Brackets(arg); Formula f = new Formula(PrefixUnaryOp.getInstance(opName,arg)); push(f); }
This method handles the key presses.
private void keydn(int key) { if (key == '\r') return; switch (activeFormula) { case -1: // Cursor is inactive switch( (char)key ) { case '\n': dup(); break; case (char)8: drop(); break; case '"': { FormulaView view; Formula f; StringPart str = new StringPart(""); view = push(f = new Formula(str)); setActiveFormula(getComponentCount()-1); view.setCursorPart(str,1); } break; case '(': case '{': case '[': { FormulaView view; Formula f; Brackets br = new Brackets((char)key); view = push(f = new Formula(br)); setActiveFormula(getComponentCount()-1); view.setCursorPart(br.getChild()); } break; default: if ((""+(char)key).equals(InfixBinaryOp.completePrefix(""+(char)key))) { doInfixBinOp(""+(char)key); } else if (key >= 32) { Formula f = new Formula(""+(char)key); FormulaView view = push(f); setActiveFormula(getComponentCount()-1); view.setCursorPart(f.getRootPart(),HasCursorPos.Somewhere_RIGHT); // zorg ervoor dat de cursorpositie goed berekend wordt, zodat // verderop de cursor correct in beeld wordt gescrolld doLayout(); } break; } break; default: // cursor is inside some formula { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); FormulaView view = (FormulaView)getComponent(activeFormula); try { boolean processed = view.keydn(key); if (processed) break; } finally { // even if an exception occurs in keydn(), restore the cursor setCursor(Cursor.getDefaultCursor()); } Formula f = view.getFormula(); if ((char)key == '\n') { view.cursorOff(); setActiveFormula(-1); if (f.getRootPart() instanceof Identifier) { Identifier ident = (Identifier)f.getRootPart(); if (ident.getString().equals(";")) { pop(); Part arg = pop().getFormula().getRootPart(); push(new Formula(arg.evaluate(variables))); } else interpretCommand(ident.getString()); } else if (f.getRootPart() instanceof OperatorSemicolon) { pop(); Part p = f.getRootPart(); OperatorSemicolon semi = (OperatorSemicolon)p; if (semi.child instanceof Identifier) { Identifier ident = (Identifier)semi.child; if (ident.getString().equals("import")) { // PENDING } } push(new Formula(semi.child.evaluate(variables))); } } else if (Part.isEmpty(f.getRootPart()) && key==8) { // Backspace on empty formula drop(); activeFormula = -1; } } } // Scroll if necessary to show the cursor if (activeFormula >= 0) { FormulaView f = (FormulaView)getComponent(activeFormula); showPart(f.getCursorPart(),activeFormula); } else ;//showFormula(formulas.getSize()-1); // to be tested repaint(); } void interpretCommand(String freshInput) { // PENDING: search for a class called e.g. ``op_plot.class'' // and call its ``command'' method or something like that //System.out.println("command >"+freshInput+"<"); if (freshInput.equals("plot")) { pop(); // remove command ("plot") from stack new PlotWindow(pop().getFormula()); } else if (freshInput.equals("truthtab")) { pop(); truthtab(); } else if (freshInput.equals("isisomorph")) { pop(); isisomorph(); } else if (freshInput.equals("complement")) { pop(); complement(); } else if (freshInput.equals("completegraph")) { pop(); completegraph(); } else if (freshInput.equals("isregular")) { pop(); isregular(); } else if (freshInput.equals("issrg")) { pop(); issrg(); } else if (freshInput.equals("petersen")) { pop(); petersen(); } else if (freshInput.equals("dup2")) { pop(); dup2(); } else if (freshInput.equals("sto")) { pop(); Formula f1 = pop().getFormula(); // varname Formula f2 = pop().getFormula(); // content if (f1.getRootPart() instanceof Identifier) { String varname = ((Identifier)f1.getRootPart()).getString(); storeVar(varname,f2.getRootPart()); } } else if (freshInput.equals("rcl")) { pop(); Formula f1 = pop().getFormula(); // varname if (f1.getRootPart() instanceof Identifier) { String varname = ((Identifier)f1.getRootPart()).getString(); Object content = getVar(varname); if (content instanceof Formula) push((Formula)content); // probably obsolete if (content instanceof Part) push(new Formula((Part)content)); } } else if (freshInput.equals("save")) { pop(); String filename = getFileName(pop().getFormula(),true); if (filename == null) return; Formula f = pop().getFormula(); try { BufferedWriter writ = new BufferedWriter(new FileWriter(filename)); f.getRootPart().write(writ); writ.close(); } catch (IOException err) {} } else if (freshInput.equals("load")) { pop(); String filename; filename = getVarName(pop().getFormula(),true); if (filename == null) return; Part p = load(filename); if (p != null) push(new Formula(p)); } else if (freshInput.equals("freevar")) { pop(); Formula f = pop().getFormula(); push(new Formula(new Brackets(LogicFunctions.freevar(f.getRootPart()),Brackets.CURLY))); } else if (InfixBinaryOp.isOperatorName(freshInput)) { pop(); doInfixBinOp(freshInput); } else if (PostfixUnaryOp.isOperatorName(freshInput)) { pop(); doPostfixUnOp(freshInput); } else if (PrefixUnaryOp.isOperatorName(freshInput)) { pop(); doPrefixUnOp(freshInput); } }
Note: we choose .text as extension because we may read .text files as an applet in Netscape without prompting the user with SecurityManager.enablePrivilege("UniversalPropertyRead") See http://developer.netscape.com/docs/technote/java/getresource/getresource.html (Thanks to the Java FAQ!)
public Part load(String varname) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); String filename = varname + ".text"; BufferedReader rd = null; try { rd = new BufferedReader(new FileReader(filename)); } catch (Exception exc) { System.out.println("load error (file): "+exc); exc.printStackTrace(); } if (rd == null) try { // Add "/" in front of the resource, so we search files in the root directory of the jar archive, // not in the "gve/calc/" directory ... rd = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/"+filename))); try { rd.ready(); } catch (NullPointerException exc) { rd = null; } // file does not exist } catch (Exception exc) { System.out.println("load error (getResourceAsStream): "+exc); exc.printStackTrace(); } if (rd == null) try { Object content = new java.net.URL(Main.documentBase,filename).getContent(); if (content instanceof InputStream) rd = new BufferedReader(new InputStreamReader((InputStream)content)); } catch (Exception exc) { System.out.println("load error (URL): "+exc); exc.printStackTrace(); } if (rd != null) try { Part result = Part.read(rd); rd.close(); setCursor(Cursor.getDefaultCursor()); return result; } catch (Exception exc) { System.out.println("load error: "+exc); exc.printStackTrace(); } setCursor(Cursor.getDefaultCursor()); return null; } public String getFileName(Formula f,boolean load) { String filename; if (f.getRootPart() instanceof StringPart) return ((StringPart)f.getRootPart()).getString() + ".text"; Component comp = this; while (!(comp instanceof Frame)) comp = comp.getParent(); FileDialog fd = new FileDialog((Frame)comp,"",load?FileDialog.LOAD:FileDialog.SAVE); fd.show(); if (fd.getDirectory()==null || fd.getFile()==null) return null; return new File(fd.getDirectory(),fd.getFile()).getPath(); } public String getVarName(Formula f,boolean load) { String filename; if (f.getRootPart() instanceof StringPart) return ((StringPart)f.getRootPart()).getString(); Component comp = this; while (!(comp instanceof Frame)) comp = comp.getParent(); FileDialog fd = new FileDialog((Frame)comp,"",load?FileDialog.LOAD:FileDialog.SAVE); fd.show(); if (fd.getDirectory()==null || fd.getFile()==null) return null; String fnam = new File(fd.getDirectory(),fd.getFile()).getPath(); if (fnam.endsWith(".text")) fnam = fnam.substring(0,fnam.length()-5); return fnam; } public void errormsg(String msg) { pushstring(msg); } public void pushstring(String str) { Formula f = new Formula(str); push(f); } private void getidents(Part p,Vector v) { if (p instanceof OperatorBoolean) { OperatorBoolean bool = (OperatorBoolean)p; getidents(bool.left,v); getidents(bool.right,v); } else if (p instanceof OperatorNot) { OperatorNot not = (OperatorNot)p; getidents(not.child,v); } else if (p instanceof Identifier) { Identifier ident = (Identifier)p; if (v.indexOf(ident.getString()) < 0) v.addElement(ident.getString()); } } public void truthtab() { Part [] formulas; { Formula f = pop().getFormula(); formulas = OperatorComma.chopCommas(f.getRootPart()); } // Find out which identifiers the formula contains String [] idents; { Vector v = new Vector(); for (int i = 0; i < formulas.length; i++) getidents(formulas[i],v); idents = new String[v.size()]; v.copyInto(idents); } int matheight = 1; for (int i = idents.length; i>0; i--) matheight *= 2; Matrix mat = new Matrix(matheight+1,idents.length+formulas.length); // top line for (int j = 0; j < idents.length; j++) mat.setElementAt(0,j,new Identifier(idents[j])); for (int j = 0; j < formulas.length; j++) mat.setElementAt(0,j+idents.length,(Part)formulas[j].clone()); // k-th line for (int k = 0; k < matheight; k++) { Evaluator eval = new MultiEvaluator(); for (int j = 0; j < idents.length; j++) { boolean val = (k & (1<<j)) == 0; mat.setElementAt(k+1,j,new Boolean(val)); eval.defineVariable(idents[j],new Boolean(val)); } for (int j = 0; j < formulas.length; j++) { Object o = formulas[j].evaluate(eval); if (o instanceof Part) mat.setElementAt(k+1,j+idents.length,(Part)o); } } // PENDING Formula result = new Formula(mat); push(result); } public void isisomorph() { if (getComponentCount() < 2) { errormsg("isisomorph: Need 2 arguments"); return; } Formula f1 = pop().getFormula(); Formula f2 = pop().getFormula(); if (f1.getRootPart() instanceof Graph && f2.getRootPart() instanceof Graph) { Graph g1 = (Graph)f1.getRootPart(); Graph g2 = (Graph)f2.getRootPart(); Object reason = null; ObjectObject oo = new ObjectObject(); boolean result = g1.isIsomorph(g2,oo); reason = oo.obj; Part why = null; if (reason instanceof String) why = new Identifier((String)reason); else if (reason instanceof int []) why = new GraphMorphism(g1,g2,(int [])reason); if (why != null) storeVar("why",why); pushstring(result ? "true" : "false"); } else errormsg("isisomorph: arguments must be Graph objects"); } public void complement() { if (getComponentCount() < 1) { errormsg("complement: Need an argument"); return; } Formula f = pop().getFormula(); if (!(f.getRootPart() instanceof Graph)) { errormsg("complement: argument must be a Graph"); return; } Graph graph = (Graph)(f.getRootPart()); graph.complement(); push(f); } public void petersen() { Graph petersen = new Graph(10,false); // undirected, 10 vertices { int [][] adj = {{0,1},{1,2},{2,3},{3,4},{4,0}, {0,5},{1,6},{2,7},{3,8},{4,9}, {5,7},{7,9},{9,6},{6,8},{8,5}}; for (int i = adj.length-1; i>=0; i--) { petersen.setAdjacent(adj[i][0],adj[i][1],true); } } Formula f = new Formula(petersen); { double twopin = Math.PI*2.0/5.0; int width=100,height=100; for (int i = 4; i >= 0; i--) { petersen.setXcoord(i,(int)(width/2 + (width/2-Graph.VERTEXDIAM)*Math.cos(i*twopin))); petersen.setYcoord(i,(int)(height/2 + (height/2-Graph.VERTEXDIAM)*Math.sin(i*twopin))); petersen.setXcoord(i+5,(int)(width/2 + (width/2-Graph.VERTEXDIAM)*Math.cos(i*twopin)/2.0)); petersen.setYcoord(i+5,(int)(height/2 + (height/2-Graph.VERTEXDIAM)*Math.sin(i*twopin)/2.0)); } } push(f); }
Returns value for k
public int isregular() { if (getComponentCount() < 1) { errormsg("isregular: Need an argument"); storeVar("why",null); return -1; } Formula f = pop().getFormula(); if (!(f.getRootPart() instanceof Graph)) { errormsg("isregular: argument must be a Graph"); storeVar("why",null); return -1; } Graph graph = (Graph)(f.getRootPart()); int size = graph.getSize(); int k = -1; for (int i = size-1; i>=0; i--) { // With how many vertices is this vertex adjacent? int count = 0; for (int j = size-1; j>=0; j--) { if (i == j) continue; if (graph.isAdjacent(i,j)) count++; } if (k < 0) k = count; else if (k != count) { storeVar("why",new Identifier("Possible degrees are "+k+" and "+count)); pushstring("false"); return -1; } } pushstring("true"); storeVar("why",new Identifier("degree is "+k)); return k; } public boolean issrg() { if (getComponentCount() < 1) { errormsg("issrg: Need an argument"); storeVar("why",null); return false; } Formula f = pop().getFormula(); if (!(f.getRootPart() instanceof Graph)) { errormsg("issrg: argument must be a Graph"); storeVar("why",null); return false; } push(f); int degree = isregular(); if (degree < 0) return false; // not even regular pop(); // remove isregular() result from the stack Graph graph = (Graph)(f.getRootPart()); int size = graph.getSize(); // Two adjacent vertices are adjacent to lambda other vertices int lambda = -1; // Two non-adjacent vertices are adjacent to lambda other vertices int mu = -1; for (int i = size-1; i>=0; i--) { for (int j = size-1; j>=0; j--) { if (i == j) continue; // i and j are adjacent to 'both' other vertices int both = 0; for (int k = size-1; k>=0; k--) { if (k==i || k==j) continue; if (graph.isAdjacent(k,i) && graph.isAdjacent(k,j)) both++; } if (graph.isAdjacent(i,j)) { if (lambda < 0) lambda = both; else if (both != lambda) { pushstring("false"); storeVar("why",new Identifier("Degree "+degree+" but possible values for lambda are a.o. "+both+" and "+lambda)); return false; } } else { if (mu < 0) mu = both; else if (both != mu) { pushstring("false"); storeVar("why",new Identifier("Degree "+degree+" but possible values for mu are a.o. "+both+" and "+mu)); return false; } } } } pushstring("true"); storeVar("why",new Identifier("degree="+degree+"; lambda="+lambda+"; mu="+mu)); return true; } public void completegraph() { if (getComponentCount() < 1) { errormsg("completegraph: Need an argument"); return; } Formula f = pop().getFormula(); Part arg = (f.getRootPart().evaluate(new MultiEvaluator())); if (!(arg instanceof Real)) { //System.out.println("completegraph "+f.getRootPart()); errormsg("completegraph: argument must be a number"); return; } int n = (int)((Real)arg).getDoubleValue(); if (n <= 0) { errormsg("completegraph: argument must be a positive number"); return; } Graph gr = new Graph(n); f = new Formula(gr); for (int i = n-1; i>=0; i--) { for (int j = n-1; j>=0; j--) gr.setAdjacent(i,j,i!=j); } push(f); } public void storeVar(String varname,Part content) { variables.setValue(varname,content); } public Part getVar(String varname) { return variables.getValue(varname); } public void setFont(Font font) { super.setFont(font); repaint(); } public void saveLatex(BufferedWriter w) throws IOException { int number = getComponentCount(); int total = number; for (int count = 0; count < getComponentCount(); count++,number--) { if (total > 1) w.write(number+":"); FormulaView view = (FormulaView)getComponent(count); w.write("$"); view.getFormula().getRootPart().saveLatex(w); w.write("$"); if (count < total-1) { w.newLine(); w.newLine(); } } } }