/* * MICO --- a free CORBA implementation * Copyright (C) 1997 Kay Roemer & Arno Puder * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Send comments and/or bug reports to: * mico@informatik.uni-frankfurt.de */ import java.util.*; import java.awt.*; import java.awt.event.*; import java.applet.Applet; import AlertBox; import EntryBox; import CanvasObject; import CanvasBoxText; import CanvasArrow; import MyCanvas; import ConceptualGraph; import CGParser; import View; import DIIFrame; class CGArrow extends CanvasArrow { public CGArrow (int x0, int y0, int x1, int y1) { super (x0, y0, x1, y1, Color.black); } } class CGNode extends CanvasBoxText { private CGNode parent; private CGArrow arrow; private Vector childs; private Vector arrows; private boolean isconcept; public int layout_y; public CGNode (int x, int y, String text, FontMetrics fm, boolean iscon) { super (x, y, text, fm, iscon ? Color.black : Color.white, iscon ? Color.white : Color.black, iscon ? Color.black : Color.white); super.Move (-Bbox().width/2, -Bbox().height/2); parent = null; childs = new Vector(); arrows = new Vector(); arrow = null; isconcept = iscon; layout_y = 0; } public void Move (int xinc, int yinc) { AutoUpdateOff (); super.Move (xinc, yinc); for (int i = arrows.size()-1; i >= 0; --i) { ((CGArrow)arrows.elementAt(i)).MoveTail (xinc, yinc); } if (arrow != null) arrow.MoveHead (xinc, yinc); AutoUpdateOn (); } public void MoveTo (int x, int y) { Point p = GetPos (); Move (x - p.x, y - p.y); } public void AddChild (CGNode n, CGArrow a) { childs.addElement (n); arrows.addElement (a); Rectangle pos = Bbox(); a.MoveTailTo (pos.x + pos.width - 1, pos.y + pos.height/2); } public void DelChild (CGNode n) throws NoSuchElementException { int idx = childs.indexOf (n); if (idx == -1) throw new NoSuchElementException (); else { childs.removeElementAt (idx); arrows.removeElementAt (idx); } } public void SetParent (CGNode n, CGArrow a) { parent = n; arrow = a; if (arrow != null) { Rectangle pos = Bbox(); arrow.MoveHeadTo (pos.x, pos.y + pos.height/2); } } public CGNode GetParent () { return parent; } public CGArrow GetParentArrow () { return arrow; } private String Quote (String s) { return s.length() > 0 ? s : "''"; } public void SetText (String text) { int width = Bbox().width; AutoUpdateOff (); super.SetText (text); width = Bbox().width - width; for (int i = arrows.size()-1; i >= 0; --i) ((CGArrow)arrows.elementAt(i)).MoveTail (width, 0); AutoUpdateOn (); } public String GetType () { return GetType (false); } public String GetType (boolean removequotes) { String t = GetText (); int idx = t.indexOf (':'); if (idx >= 0) t = t.substring (0, idx); if (removequotes) { t = t.trim(); if (t.charAt (0) == '"' || t.charAt (0) == '\'') t = t.substring (1, t.length()-1); } return t; } public void SetType (String t) { String inst = GetInst (); if (inst != null) SetText (Quote (t) + ":" + inst); else SetText (Quote (t)); } public String GetInst () { String t = GetText (); int idx = t.indexOf (':'); if (idx < 0) return null; return t.substring (idx+1); } public void SetInst (String t) { if (IsConcept()) { SetText (GetType() + ":" + Quote (t)); } } public int NumChilds () { return childs.size(); } public CGNode ChildAt (int idx) throws ArrayIndexOutOfBoundsException { return (CGNode)childs.elementAt (idx); } public CGArrow ArrowAt (int idx) throws ArrayIndexOutOfBoundsException { return (CGArrow)arrows.elementAt (idx); } public boolean IsConcept () { return isconcept; } public void SortChilds () { for (int i = childs.size(); i >= 2; --i) { for (int j = 0; j < i-1; ++j) { Point p1 = ((CGNode)childs.elementAt(j)).GetPos(); Point p2 = ((CGNode)childs.elementAt(j+1)).GetPos(); if (p1.y > p2.y) { Object tmp = childs.elementAt (j); childs.setElementAt (childs.elementAt (j+1), j); childs.setElementAt (tmp, j+1); tmp = arrows.elementAt (j); arrows.setElementAt (arrows.elementAt (j+1), j); arrows.setElementAt (tmp, j+1); } } } } } public class CGEditor extends MyCanvas implements EntryBoxHandler, ActionListener, MouseListener, MouseMotionListener, FocusListener, KeyListener { private DIIFrame frame; private CGNode topnode; private CGNode curnode; private CGArrow curarrow; private int dragx, dragy; private boolean readonly; private Vector viewers; private Hashtable menus, menumap; private PopupMenu popup; private final static int stateIdle = 0; private final static int stateDrag = 1; private final static int stateCreate = 2; private final static int stateEdit = 3; private int state; private final int layoutXDist = 20; private final int layoutYDist = 20; public CGEditor (Dimension prefsize, boolean editable, DIIFrame f) { super (prefsize); frame = f; readonly = !editable; state = stateIdle; topnode = null; curnode = null; curarrow = null; viewers = new Vector(); menus = new Hashtable(); menumap = new Hashtable(); popup = null; this.addMouseListener( this ); this.addMouseMotionListener( this ); this.addFocusListener( this ); this.addKeyListener( this ); } public void focusGained( FocusEvent e ) { } public void focusLost( FocusEvent e ) { if (readonly) return; ShowStatus (-1, -1); } public void keyPressed( KeyEvent e ) { if (readonly) return; if (e.getKeyChar() == '\033') Abort (); else if (e.getKeyChar() == 'l') Layout (null); } public void keyReleased( KeyEvent e ) { } public void keyTyped( KeyEvent e ) { } public void mouseClicked (MouseEvent e) { } public void mouseEntered (MouseEvent e) { } public void mouseExited (MouseEvent e) { } public void mousePressed (MouseEvent e) { if (readonly) return; Pick( e ); } public void mouseReleased (MouseEvent e) { if (readonly) return; Drop( e ); } public void mouseDragged (MouseEvent e) { if (readonly) return; Drag( e ); } public void mouseMoved (MouseEvent e) { if (readonly) return; ShowStatus( e.getX(), e.getY() ); } public void actionPerformed( ActionEvent e ) { String s = e.getActionCommand(); if (s == null) return; String menuname = (String)menumap.get (curnode.GetType(true)); Hashtable templates = (Hashtable)menus.get (menuname); ConceptualGraph cg = (ConceptualGraph)templates.get(s); AutoUpdateOff (); CGNode top = ConvertInput (cg); CGArrow arrow = new CGArrow (0, 0, 0, 0); Add (arrow); ToBottom (arrow); curnode.AddChild (top, arrow); top.SetParent (curnode, arrow); Layout (curnode); AutoUpdateOn (); NotifyViewers (); } private void ShowStatus (int x, int y) { String s = ""; CGNode node = null; if (x >= 0 && y >= 0) { try { CanvasObject co = super.Find (x, y); if (co instanceof CGNode) { node = (CGNode)co; } } catch (NoSuchElementException ex) {} if (node == null) { if (topnode == null) s = "Button1: create"; } else { s = "Button1: drag"; if (node.IsConcept()) { if (node.GetInst() != null) s += ", Shift-Button1: edit"; if (node == topnode) s += ", Shift-Ctrl-Button1: remove"; if (menumap.containsKey (node.GetType (true))) s += ", Button3: menu"; } else { s += ", Shift-Ctrl-Button1: remove"; } } } frame.status.setStatus (s); } private void MenuPopup (MouseEvent ev, CGNode node) { if (!node.IsConcept()) return; String menuname = (String)menumap.get (node.GetType(true)); if (menuname == null) return; Hashtable templates = (Hashtable)menus.get (menuname); if (templates == null) return; if( popup != null ) remove( popup ); popup = new PopupMenu( menuname ); Enumeration e = templates.keys(); while (e.hasMoreElements()) { String key = (String)e.nextElement(); MenuItem mi = new MenuItem( key ); mi.setActionCommand( key ); mi.addActionListener( this ); popup.add( mi ); } curnode = node; this.add( popup ); popup.show( this, ev.getX(), ev.getY() ); } private void Pick (MouseEvent ev) { if (state != stateIdle) return; CGNode node = null; try { CanvasObject co = super.Find (ev.getX(), ev.getY()); if (co instanceof CGNode) { node = (CGNode)co; } } catch (NoSuchElementException ex) {} if (node != null && node.IsConcept() && (ev.getModifiers() & Event.META_MASK) != 0) { // template menu MenuPopup (ev, node); } else { switch (ev.getModifiers() & (Event.SHIFT_MASK | Event.CTRL_MASK)) { case Event.SHIFT_MASK: // edit if (node != null) { String s = node.GetInst(); if (s != null) { curnode = node; Edit (s); } } break; case Event.CTRL_MASK: // drag if (node != null) { state = stateDrag; ToTop (node); curnode = node; dragx = ev.getX(); dragy = ev.getY(); } break; case Event.SHIFT_MASK|Event.CTRL_MASK: // delete subtree if (node != null) { // ZZZ only allowed for relations or topnode if (node.IsConcept() && node != topnode) break; CGNode parent = node.GetParent (); if (parent == null || parent.IsConcept() || parent.NumChilds() > 1) { AutoUpdateOff (); if (parent != null) { parent.DelChild (node); Remove (node.GetParentArrow()); } RemoveFromCanvas (node); AutoUpdateOn (); } if (node == topnode) topnode = null; NotifyViewers(); } break; default: if (node == null && topnode == null) { // create topnode topnode = new CGNode (ev.getX(), ev.getY(), "OPERATION:deposit", getFontMetrics (getFont()), true); Add (topnode); NotifyViewers (); } else if (node != null) { // ZZZ drag state = stateDrag; ToTop (node); curnode = node; dragx = ev.getX(); dragy = ev.getY(); } /* * ZZZ creating subnodes not allowed else if (node != null && topnode != null) { state = stateCreate; curnode = node; Rectangle pos = node.Bbox(); curarrow = new CGArrow (pos.x+pos.width-1, pos.y+pos.height/2, ev.x, ev.y); Add (curarrow); } */ break; } } } private void Drag (MouseEvent ev) { switch (state) { case stateDrag: curnode.Move (ev.getX() - dragx, ev.getY() - dragy); dragx = ev.getX(); dragy = ev.getY(); break; case stateCreate: curarrow.MoveHeadTo (ev.getX(), ev.getY()); break; } } private void Drop (MouseEvent ev) { switch (state) { case stateDrag: state = stateIdle; CheckResize (); break; case stateCreate: CGNode node = new CGNode (ev.getX(), ev.getY(), "something", getFontMetrics (getFont()), !curnode.IsConcept()); curnode.AddChild (node, curarrow); node.SetParent (curnode, curarrow); ToBottom (curarrow); Add (node); if (!node.IsConcept()) { int x = ev.getX() + node.Bbox().width + layoutXDist; CGNode node2 = new CGNode (x, ev.getY(), "something", getFontMetrics (getFont()), true); CGArrow arrow2 = new CGArrow (0, 0, 0, 0); node.AddChild (node2, arrow2); node2.SetParent (node, arrow2); Add (node2); Add (arrow2); ToBottom (arrow2); } state = stateIdle; NotifyViewers(); break; } ShowStatus (ev.getX(), ev.getY()); } private void Abort () { switch (state) { case stateCreate: Remove (curarrow); state = stateIdle; break; } state = stateIdle; } public void entryBoxHandler (EntryBox e, Object arg) { if (state != stateEdit) return; state = stateIdle; if (e.GetButton() == 0) { curnode.SetInst (e.GetText()); NotifyViewers(); } } protected void Edit (String txt) { state = stateEdit; EntryBox editor = new EntryBox ("Edit Node", "Enter node:", txt, this, null, frame); editor.pack(); editor.show(); } private void RemoveFromCanvas (CGNode root) { super.Remove (root); for (int i = root.NumChilds()-1; i >= 0; --i) { super.Remove (root.ArrowAt(i)); RemoveFromCanvas (root.ChildAt(i)); } } private int LayoutPass1 (CGNode root, int y, Vector vdist, int level) { Rectangle bbox = root.Bbox(); root.layout_y = y; root.SortChilds (); if (level >= vdist.size()) { vdist.addElement (new Integer (bbox.width)); } else { int width = ((Integer)vdist.elementAt(level)).intValue(); if (width < bbox.width) { vdist.setElementAt (new Integer (bbox.width), level); } } int nchilds = root.NumChilds(), y2 = y; for (int i = 0; i < nchilds; ++i) { y2 = LayoutPass1 (root.ChildAt(i), y2, vdist, level+1); } return Math.max (y + bbox.height + layoutYDist, y2); } private void LayoutPass2 (CGNode root, int x, Vector vdist, int level) { Rectangle bbox = root.Bbox(); int width = ((Integer)vdist.elementAt(level)).intValue(); root.MoveTo (x + (width - bbox.width)/2, root.layout_y); int nchilds = root.NumChilds(); for (int i = 0; i < nchilds; ++i) { LayoutPass2 (root.ChildAt(i), x+width+layoutXDist, vdist, level+1); } } public void Layout (CGNode root) { int x, y; if (root == null) { if ((root = topnode) == null) return; x = layoutXDist; y = layoutYDist; } else { x = root.Bbox().x; y = root.Bbox().y; } AutoUpdateOff (); Vector vdist = new Vector(); LayoutPass1 (root, y, vdist, 0); LayoutPass2 (root, x, vdist, 0); CheckResize (); AutoUpdateOn (); } private CGNode ConvertInput (ConceptualGraph cg) { StringBuffer ostr = new StringBuffer(); cg.getNode().print (ostr); String s = ostr.toString(); s = s.substring (1, s.length()-1); CGNode node = new CGNode (0, 0, s, getFontMetrics(getFont()), cg.getNode() instanceof ConceptNode); Add (node); ConceptualGraph cg2; for (cg2 = cg.getWidth(); cg2 != null; cg2 = cg2.getDepth()) { CGNode node2 = ConvertInput (cg2); CGArrow arrow2 = new CGArrow (0, 0, 0, 0); Add (arrow2); ToBottom (arrow2); node.AddChild (node2, arrow2); node2.SetParent (node, arrow2); } return node; } private void ConvertOutput (StringBuffer ostr, CGNode node) { node.SortChilds (); String s = node.GetText(); if (node.IsConcept()) { ostr.append ("[" + s + "]"); } else { ostr.append ("(" + s + ")"); } int nchilds = node.NumChilds(); if (nchilds == 1) { ostr.append ("->"); ConvertOutput (ostr, node.ChildAt (0)); } else if (nchilds > 1) { ostr.append ("-"); for (int i = 0; i < nchilds; ++i) { ostr.append ("->"); ConvertOutput (ostr, node.ChildAt (i)); if (i+1 != nchilds) ostr.append (","); } ostr.append ("."); } } public void SetCG (ConceptualGraph cg) { AutoUpdateOff (); if (topnode != null) { RemoveFromCanvas (topnode); topnode = null; } if (cg != null) { topnode = ConvertInput (cg); Layout (null); } AutoUpdateOn (); Abort(); NotifyViewers(); } public ConceptualGraph GetCG () { if (topnode == null) return null; StringBuffer ostr = new StringBuffer(); ConvertOutput (ostr, topnode); CGParser p = new CGParser (); try { return p.Parse (ostr.toString()); } catch (Exception e) { AlertBox a = new AlertBox ("Parse Error", "Cannot parse graph"); a.pack(); a.show(); return null; } } public void AddView (View v) { viewers.addElement (v); } public void DelView (View v) { viewers.removeElement (v); } private void NotifyViewers () { for (int i = 0; i < viewers.size(); ++i) ((View)viewers.elementAt(i)).UpdateView (this); } public void AddTemplate (String menuname, Vector after, String entryname, ConceptualGraph cg) { Hashtable menu = (Hashtable)menus.get (menuname); if (menu == null) { menu = new Hashtable (); menus.put (menuname, menu); } menu.put (entryname, cg); for (int i = 0; i < after.size(); ++i) { String concept = (String)after.elementAt (i); if (!menumap.containsKey (concept)) menumap.put (concept, menuname); } } public void DelTemplate (String name) throws NoSuchElementException { menus.remove (name); Enumeration keys = menumap.keys (); Enumeration elms = menumap.elements (); while (keys.hasMoreElements() && elms.hasMoreElements()) { String key = (String)keys.nextElement(); String elm = (String)elms.nextElement(); if (elm.equals (name)) menumap.remove (key); } } }