1:/*
   2: * Programming graphicxal user interfaces
   3: * Example: DiagramView
   4: * Jarkko Leponiemi 2002
   5: */
   6:
   7:import java.awt.*;
   8:import java.awt.event.*;
   9:import java.util.*;
  10:import javax.swing.*;
  11:
  12:/**
  13: * A class representing the combined view and controller
  14: * of the diagram application.
  15: */
  16:public class DiagramView extends JPanel implements Observer
  17:{
  18:   // the diagram model
  19:   private Diagram diagram;
  20:   // the cached bounding rectangles for nodes
  21:   private Map rcache;
  22:   
  23:   public DiagramView(Diagram d) {
  24:      diagram = d;
  25:      d.addObserver(this);
  26:      rcache = new HashMap();
  27:      // create the controller add register as listener
  28:      Controller c = new Controller();
  29:      this.addMouseListener(c);
  30:      this.addMouseMotionListener(c);
  31:   }
  32:   
  33:   // the Observer function
  34:   // called when model has been changed
  35:   public void update(Observable o, Object arg) {
  36:      if (arg instanceof Diagram.Node) {
  37:         rcache.remove(arg);
  38:      }
  39:      repaint();
  40:   }
  41:   
  42:   public void paint(Graphics graphics) {
  43:      super.paint(graphics);
  44:      Graphics2D g = (Graphics2D) graphics;
  45:      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  46:      
  47:      // draw the connectors
  48:      Iterator i = diagram.getConnectors();
  49:      while (i.hasNext()) {
  50:         Diagram.Connector c = (Diagram.Connector) i.next();
  51:         Diagram.Node n1 = c.getNode1();
  52:         Diagram.Node n2 = c.getNode2();
  53:         g.drawLine(n1.getX(), n1.getY(), n2.getX(), n2.getY());
  54:      }
  55:      
  56:      // draw the nodes
  57:      FontMetrics fm = g.getFontMetrics();
  58:      int h = fm.getAscent();
  59:      i = diagram.getNodes();
  60:      while (i.hasNext()) {
  61:         Diagram.Node n = (Diagram.Node) i.next();
  62:         // get the bounding rectangle
  63:         Rectangle r = (Rectangle) rcache.get(n);
  64:         if (r == null)
  65:            r = calcNodeBounds(n, fm);
  66:         // draw the node
  67:         g.setColor(Color.white);
  68:         g.fill(r);
  69:         g.setColor(Color.black);
  70:         g.draw(r);
  71:         g.drawString(n.getTitle(), r.x + 3, r.y + h + 3);
  72:      }
  73:   }
  74:   
  75:   // calculate the bounds of a node in the view
  76:   private Rectangle calcNodeBounds(Diagram.Node n, FontMetrics fm) {
  77:      int sw = fm.stringWidth(n.getTitle()) + 6;
  78:      int sh = fm.getAscent() + fm.getDescent() + 6;
  79:      Rectangle r = new Rectangle(n.getX() - sw / 2, n.getY() - sh / 2, sw, sh);
  80:      rcache.put(n, r);
  81:      return r;
  82:   }
  83:   
  84:   // find the topmost node under the given point
  85:   private Diagram.Node findNodeInPoint(int x, int y) {
  86:      Diagram.Node last = null;
  87:      Iterator i = diagram.getNodes();
  88:      while (i.hasNext()) {
  89:         Diagram.Node n = (Diagram.Node) i.next();
  90:         Rectangle r = (Rectangle) rcache.get(n);
  91:         if (r != null && r.contains(x, y))
  92:            last = n;
  93:      }
  94:      return last;
  95:   }
  96:   
  97:   // an inner class representing the controller
  98:   private class Controller implements MouseListener, MouseMotionListener
  99:   {
 100:      // the modes of the controller
 101:      private static final int NODE = 0;
 102:      private static final int CONNECTOR = 1;
 103:      private static final int SELECT = 2;
 104:      
 105:      private boolean dragging = false;
 106:      private Diagram.Node n = null;
 107:      private int offsx, offsy;
 108:      private int curx, cury;
 109:      private int mode = SELECT;
 110:      private int idcnt = 1;
 111:      
 112:      Controller() {
 113:         // add entries to the inputmap and the action map
 114:         // for changing the mode with the keyboard
 115:         InputMap im = getInputMap();
 116:         im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), "node");
 117:         im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0), "connector");
 118:         im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), "select");
 119:         ActionMap am = getActionMap();
 120:         am.put("node", new ModeAction(NODE));
 121:         am.put("connector", new ModeAction(CONNECTOR));
 122:         am.put("select", new ModeAction(SELECT));
 123:      }
 124:      
 125:      // create a new node or
 126:      // start moving a node or
 127:      // start creating a connector
 128:      public void mousePressed(MouseEvent e) {
 129:         // create node
 130:         if (mode == NODE) {
 131:            diagram.createNode("Untitled" + idcnt++, e.getX(), e.getY());
 132:         } else {
 133:            n = findNodeInPoint(e.getX(), e.getY());
 134:            if (n != null) {
 135:               dragging = true;
 136:               // move node
 137:               if (mode == SELECT) {
 138:                  offsx = n.getX() - e.getX();
 139:                  offsy = n.getY() - e.getY();
 140:               }
 141:               // create connector
 142:               else if (mode == CONNECTOR) {
 143:                  curx = e.getX();
 144:                  cury = e.getY();
 145:                  drawTempConn();
 146:               }
 147:            }
 148:         }
 149:      }
 150:      
 151:      // draw or erase the temporary connector
 152:      private void drawTempConn() {
 153:         Graphics g = getGraphics();
 154:         g.setXORMode(getBackground());
 155:         g.setColor(Color.black);
 156:         g.drawLine(n.getX(), n.getY(), curx, cury);
 157:         g.dispose();
 158:      }
 159:      
 160:      public void mouseDragged(MouseEvent e) {
 161:         if (dragging) {
 162:            // drag the node
 163:            if (mode == SELECT)
 164:               n.move(e.getX() + offsx, e.getY() + offsy);
 165:             // or an image of a connector
 166:            else if (mode == CONNECTOR) {
 167:               drawTempConn();
 168:               curx = e.getX();
 169:               cury = e.getY();
 170:               drawTempConn();
 171:            }
 172:         }
 173:      }
 174:      
 175:      public void mouseReleased(MouseEvent e) {
 176:         if (dragging) {
 177:            dragging = false;
 178:            // try to create a new connector
 179:            if (mode == CONNECTOR) {
 180:               drawTempConn();
 181:               Diagram.Node n2 = findNodeInPoint(e.getX(), e.getY());
 182:               if (n2 != null)
 183:                  diagram.createConnector(n, n2);
 184:            }
 185:            n = null;
 186:         }
 187:      }
 188:      
 189:      public void mouseClicked(MouseEvent e) {}
 190:      public void mouseEntered(MouseEvent e) {}
 191:      public void mouseExited(MouseEvent e) {}
 192:      public void mouseMoved(MouseEvent e) {}
 193:      
 194:      // an inner class forn changing the mode
 195:      private class ModeAction extends AbstractAction
 196:      {
 197:         private int m;
 198:         ModeAction(int m) {
 199:            this.m = m;
 200:         }
 201:         
 202:         public void actionPerformed(ActionEvent e) {
 203:            mode = this.m;
 204:         }
 205:      }
 206:   }
 207:}