/*******************************************************************************
+
+  LEDA 3.5
+
+  edit.c
+
+  This file is part of the LEDA research version (LEDA-R) that can be 
+  used free of charge in academic research and teaching. Any commercial
+  use of this software requires a license which is distributed by the
+  LEDA Software GmbH, Postfach 151101, 66041 Saarbruecken, FRG
+  (fax +49 681 31104).
+
+  Copyright (c) 1991-1997  by  Max-Planck-Institut fuer Informatik
+  Im Stadtwald, 66123 Saarbruecken, Germany     
+  All rights reserved.
+ 
*******************************************************************************/
#include <LEDA/graphwin.h>
#include "../../src/graphwin/local.h"
#include <math.h>


void GraphWin::get_bounding_box(double& xmin, double& ymin, 
                                double& xmax, double& ymax)
{
  window& W = get_window();
  graph&  G = get_graph();
  node    v = G.first_node();

  xmin = xmax = ymin = ymax = 0;

  if (v == nil) return;

  point p = get_position(v);

  xmin = p.xcoord();
  xmax = p.xcoord();
  ymin = p.ycoord();
  ymax = p.ycoord();

  forall_nodes(v,G)
  { point  p  = get_position(v);
    double x  = p.xcoord();
    double y  = p.ycoord();
    double r1 = 2*get_radius1(v);
    double r2 = 2*get_radius2(v);
    double tw = W.text_width(get_label(v));
    double th = W.text_height(get_label(v));

    r1 += Max(tw,r1);
    r2 += Max(th,r2);

    if (x-r1 < xmin) xmin = x-r1;
    if (x+r1 > xmax) xmax = x+r1;
    if (y-r2 < ymin) ymin = y-r2;
    if (y+r2 > ymax) ymax = y+r2;
   }
 
  double d = W.pix_to_real(4);

  edge e;
  forall_edges(e,G) 
  { list<point> P = get_bends(e);
    point p;
    forall(p,P) 
    { double x=p.xcoord();
      double y=p.ycoord();
      if (x < xmin) xmin = x - d;
      if (x > xmax) xmax = x + d;
      if (y < ymin) ymin = y - d; 
      if (y > ymax) ymax = y + d;
     }
   }
}


//----------------------------------------------------------------------------

static void dfs(graph& G, node v, node_array<bool>& reached, list<node>& L) { 
  reached[v] = true;
  L.append(v);
  edge e;
  forall_inout_edges(e,v) { 
    node u = G.opposite(v,e);
    if (!reached[u]) dfs(G,u,reached,L);
  }
}

//----------------------------------------------------------------------------

void gw_setup_component(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  graph& G=gw.get_graph();
  list<node> V;
  node_array<bool> n_reached(G,false);
  dfs(G,v,n_reached,V);
  gw_setup(gw,V);
}

//----------------------------------------------------------------------------


void gw_move_nodes(GraphWin& gw, const point& p, const list<node>& V) 
{
  if (V.empty()) return;

  graph&  G = gw.get_graph();
  window& W = gw.get_window();

  int n = G.number_of_nodes();

  if (n > 1 && n == V.length()) // move graph
  { gw_scroll_graph(gw,p,false);
    return;
   }

  node_array<bool> in_V(G,false);
  node v;
  forall(v,V) in_V[v] = true;

  list<edge> E;       // edges to move

  int move_count = 0; // number of items to move

  forall(v,V) 
  { move_count++;
    edge e;
    forall_adj_edges(e,v) 
    { move_count++;
      node w= target(e);
      if (in_V[v] && in_V[w]) E.append(e);
     }
   }

  bool nodes_only = move_count > gw.get_max_move_items();

  double x = p.xcoord();
  double y = p.ycoord();

  unsigned long t = W.button_press_time();
  int event;

  gw.n_animation_start(V);

  do { double x1,y1;
       int val;
       unsigned long t1;
       event = W.read_event(val,x1,y1,t1);
       if (event == motion_event) if (t1 - t < 50) continue;
       vector vec(x1-x,y1-y);
       t = t1;
       x = x1;
       y = y1;
       node v;
       forall(v,V) gw.move_node(v,vec);
       edge e;
       forall(e,E) gw.move_edge(e,vec);
       gw.n_animation_step(nodes_only);
  } while (event != button_release_event);

  gw.n_animation_end();
}



void gw_move_node(GraphWin& gw, const point& p) 
{
  graph&  G = gw.get_graph();
  window& W = gw.get_window();

  node v = gw.get_found_node();

  if (v == nil) return;


  if (gw.is_selected(v) && gw.get_selected_nodes().length() > 1)
  { gw_move_nodes(gw,p,gw.get_selected_nodes()); 
    return; 
   }

  if (!gw.check_start_move_node_handler(v)) return;

  gw.call_start_move_node_handler(v);

  // hide labels of adjacent edges temporarily
  list<int> ltype;
  bool b = gw.set_flush(false);
  edge e;
  forall_adj_edges(e,v)
  { ltype.append(gw.get_label_type(e));
    gw.set_label_type(e,no_label);
   }
  forall_in_edges(e,v)
  { ltype.append(gw.get_label_type(e));
    gw.set_label_type(e,no_label);
   }
  gw.set_flush(b);


  bool move_handler = gw.has_move_node_handler();

  gw.n_animation_start(v);


  unsigned long t = W.button_press_time();
  unsigned long t1;

  point  q  = gw.get_position(v);
  double x  = p.xcoord();
  double y  = p.ycoord();
  double dx = q.xcoord() - x;
  double dy = q.ycoord() - y;

  int event,val;
  do {
    do { event = W.read_event(val,x,y,t1);
         if (event == motion_event) if (t1 - t < 50) continue;
         t = t1;
         q = point(x+dx,y+dy);
         if (gw.check_move_node_handler(v,q))
         { gw.set_position(v,q);
           if (move_handler)
             gw.redraw();
           else
             gw.n_animation_step();
           gw.call_move_node_handler(v);
         }
       } while (event != button_release_event);
   } while (!gw.check_end_move_node_handler(v,q));


  // restore labels of adjacent edges

  forall_adj_edges(e,v) 
       gw.set_label_type(e,(gw_label_type)ltype.pop());
  forall_in_edges(e,v) 
       gw.set_label_type(e,(gw_label_type)ltype.pop());

  gw.n_animation_end();

  gw.call_end_move_node_handler(v);
}


 

//----------------------------------------------------------------------------

static list_item min_bend_dist(const list<point>& P, const point& p) {
  if (P.empty()) return nil;
  list_item it,min_it;
  double min,d;
  min=p.distance(P[it=min_it=P.first()]);
  while ((it=P.succ(it)) != nil) {
    d=p.distance(P[it]);
    if (d < min) {
      min    = d;
      min_it = it;
    }
  }
  return min_it;
}


static list_item min_poly_dist(const list<point>& P, const point& p) {
  if (P.size() < 2) return nil;
  list_item it,it2,min_it;
  double min=MAXDOUBLE,d;
  double x_min,y_min,x_max,y_max;

  it2=P.succ(it=P.first());

  do {
    const point& p1 = P[it];
    const point& p2 = P[it2];

    if (p1.xcoord() > p2.xcoord()) {
      x_min=p2.xcoord();
      x_max=p1.xcoord();
    }
    else {
      x_min=p1.xcoord();
      x_max=p2.xcoord();
    }
    
    if (p1.ycoord() > p2.ycoord()) {
      y_min=p2.ycoord();
      y_max=p1.ycoord();
    }
    else {
      y_min=p1.ycoord();
      y_max=p2.ycoord();
    }
    
    bool b=true;
    
    if (y_max-y_min > x_max-x_min) {
        if ((p.ycoord()<y_min)||(p.ycoord()>y_max)) b=false;
    }
    else {
      if ((p.xcoord()<x_min)||(p.xcoord()>x_max)) b=false;
    }
      

    if (b) {
      d = line(p1,p2).perpendicular(p).length();
      if (d < min) {
	min=d;
	min_it=it;
      }
    }

    it=it2;
    it2=P.succ(it);
    
  }
  while (it2 != nil);

  return min_it;
}


void gw_move_edge(GraphWin& gw, const point& p) 
{
  edge e = gw.get_found_edge();

  if (e == nil) return;
  
  graph&  G = gw.get_graph();
  window& W = gw.get_window();

  // find out whether p is near to a bend of the polygon of e

  list<point> P = gw.get_edge_points(e);

  point head=P.pop();
  point tail=P.Pop();

  list_item it=min_bend_dist(P,p);

  bool old_flush=gw.set_flush(false);

  if (it != nil) {  // P is not a straight line
    if (p.distance(P[it]) > 10.0/W.scale()) {
      P.push(head);
      P.append(tail);
      it=min_poly_dist(P,p);
      it=P.insert(p,it); 
      P.pop();P.Pop();
      gw.set_bends(e,P);
    }
  }
  else { 
    it=P.push(p); 
    gw.set_bends(e,P); 
  }

  point& q=P[it];

  unsigned long t=W.button_press_time(),t1;
  int val,event;
  double x,y;

  node v = source(e);
  node w = target(e);

  gw.e_animation_start(e);

  double xmin=W.xmin();
  double ymin=W.ymin();

  do {
    event = W.read_event(val,x,y,t1);
    if (event == motion_event) if (t1 - t < 50) continue;
    q = point(x,y);
    gw.set_bends(e,P);
    gw.e_animation_step();
    t = t1;
  }
  while (event != button_release_event);

  gw.e_animation_end();
}

//----------------------------------------------------------------------------

void gw_select_component(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  graph& G=gw.get_graph();
  list<node> V;
  node_array<bool> n_reached(G,false);
  dfs(G,v,n_reached,V);
  bool select=false;
  forall(v,V) if (!gw.is_selected(v)) { select=true; break; }
  bool save_flush=gw.set_flush(false);
  forall(v,V) gw.set_select(v,select);  
  gw.redraw();
  gw.set_flush(true);
}

//----------------------------------------------------------------------------

void gw_move_component(GraphWin& gw, const point& p) {

  node v=gw.get_found_node();

  if (v == nil) {
    edge e=gw.get_found_edge();
    if (e == nil) return;
    v=source(e);
  }

  if (!gw.check_move_component_handler(v)) return;

  graph& G = gw.get_graph();
  list<node> V;
  node_array<bool> n_reached(G,false);
  dfs(G,v,n_reached,V);
  gw_move_nodes(gw,p,V);

  gw.call_move_component_handler(v);
}

//----------------------------------------------------------------------------

void gw_select_all_nodes(GraphWin& gw,   const point&) 
{ gw.select_all_nodes(); }

void gw_deselect_all_nodes(GraphWin& gw, const point&) 
{ gw.deselect_all_nodes(); }

//----------------------------------------------------------------------------

void gw_select_node(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  gw.set_select(v,!gw.is_selected(v));
}

//----------------------------------------------------------------------------

void gw_select_edge(GraphWin& gw, const point&) {
  edge e=gw.get_found_edge();
  if (e == nil) return;
  gw.set_select(e,!gw.is_selected(e));
}

//----------------------------------------------------------------------------

void gw_new_node(GraphWin& gw, const point& p) {
  if (gw.check_new_node_handler(p)) gw.call_new_node_handler(gw.new_node(p));
}

//----------------------------------------------------------------------------

segment GraphWin::edge_segment(node v, point pv, node w, point pw)
{
  if (v != nil && v == w) 
    return segment(pv,pv);

  double xv = pv.xcoord();
  double yv = pv.ycoord();

  double xw = pw.xcoord();
  double yw = pw.ycoord();

  line L(pv,pw);

  if (v) pv = first_hit(v,L,xw<xv,yw<yv);
  if (w) pw = first_hit(w,L,xv<xw,yv<yw);

  return segment(pv,pw);
}


void gw_new_edge(GraphWin& gw, const point&)
{
  node v = gw.get_found_node();

  if (v == nil) return;

  window& W = gw.get_window();

/*
  gw.open_msg_win();
  char* msg = "NEW EDGE   left: create edge   middle: insert bend   right: cancel";
  gw.show_msg(msg);
*/


  bool selected = gw.set_select(v,true);
  bool save_flush = gw.set_flush(false);

  node u = v;
  node w = nil;

  point p = gw.get_position(v);
  point q;

  list<point> L;
  L.push(p);

  int but;


  while (w == nil)
  { 
    W.set_mode(xor_mode);

    segment s(0,0,0,0);
    segment s1;
    double x,y;
    int event;

    for(;;)
    { event = W.read_event(but,x,y);
      q = point(x,y);
      w = gw.find_node(q);

      if (event == button_press_event) break;

      if (event != motion_event || v == w) continue;

      if (w != gw.current_node)
      { W.draw_segment(s);
        W.set_mode(src_mode);
        node x = gw.current_node; 
        gw.current_node = w;
        if (x) gw.draw_node(x);
        if (w) gw.draw_node(w);
        W.set_mode(xor_mode);
        W.draw_segment(s);
       }

      //s1 = gw.edge_segment(u,p,w,q);
      s1 = gw.edge_segment(u,p,nil,q);
      W.draw_segment(s1);
      W.draw_segment(s);
      s = s1;
     }
    W.set_mode(src_mode);

    gw.current_node = nil; 


    L.append(q);
    W.draw_segment(s);

    if (but == DEL_BUTTON) break;

    if (w == nil && but == NEW_BUTTON) 
    { if (gw.auto_create_target && gw.check_new_node_handler(q)) 
      { w = gw.new_node(q);
        gw.call_new_node_handler(w);
       }
    }

    u = w;
    p = q;
   } 

  gw.set_flush(save_flush);
  gw.set_select(v,selected);

  L.pop();
  L.Pop();

  if (but == DEL_BUTTON)
    gw.redraw();
  else
    if (gw.check_new_edge_handler(v,w) && (v != w || !L.empty())) 
    { edge e = gw.new_edge(v,w,L);
      gw.call_new_edge_handler(e);
     }

/*
  gw.close_msg_win();
*/
}


void gw_new_edge_old(GraphWin& gw, const point&)
{
  node v = gw.get_found_node();

  if (v == nil) return;

  window& W = gw.get_window();


  list<point> L;

  point p;
  point q = gw.get_position(v);

  L.push(q);

  bool selected = gw.set_select(v,true);

  int but;

  node w = nil;

  while (w == nil)
  { p = q;
    but = W.read_mouse_seg(p,q);
    L.append(q);
    W.draw_segment(p,q);
    if (but == DEL_BUTTON) break;
    w = gw.find_node(q);
    if (w == nil && but == NEW_BUTTON && gw.check_new_node_handler(q)) 
    { w =gw.new_node(q);
      gw.call_new_node_handler(w);
     }
   } 

  gw.set_select(v,selected);

  L.pop();
  L.Pop();

  if (but == DEL_BUTTON)
    gw.redraw();
  else
    if (gw.check_new_edge_handler(v,w)) 
    { edge e = gw.new_edge(v,w,L);
      gw.call_new_edge_handler(e);
     }
}


//----------------------------------------------------------------------------

static void gw_del_node(GraphWin&gw, node v) {
  if (gw.check_del_node_handler(v)) {
    gw.del_node(v);
    gw.call_del_node_handler();
  }
}


void gw_del_node(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  if (gw.is_selected(v) && gw.get_selected_nodes().size()>1) {
    panel P;
    P.text_item("\\blue Remove all selected nodes ?");
    int node_b   = P.button("remove");
    int cancel_b = P.button("cancel");
    if (P.open(gw.get_window()) != cancel_b){
      bool b = gw.set_flush(false);
      list<node> L(gw.get_selected_nodes());
      forall(v,L) gw_del_node(gw,v);
      gw.redraw();
      gw.set_flush(b);
    }
  }
  else {
    gw_del_node(gw,v);
  }

} 

//----------------------------------------------------------------------------

void gw_del_edge(GraphWin& gw, const point& p) 
{
  window& W = gw.get_window();

  edge e = gw.get_found_edge();

  if (e == nil) return;

  list<point> P = gw.get_bends(e);
  list_item it = min_bend_dist(P,p);
  if (it != nil) {
    if (p.distance(P[it]) < W.pix_to_real(10))
    { P.del_item(it);
      gw.set_bends(e,P);
      return;
    }
  }

  if (gw.check_del_edge_handler(e)) {
    gw.del_edge(e);
    gw.call_del_edge_handler();
  }
} 

//----------------------------------------------------------------------------

void gw_del_component(GraphWin& gw, const point& p) {
  gw.deselect_all_nodes();
  gw_select_component(gw,p);
  gw_del_node(gw,p);
  gw.deselect_all_nodes();
}

//----------------------------------------------------------------------------

void gw_setup_node(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  if (!gw.is_selected(v)) {
    gw.deselect_all_nodes();
    gw.select(v);
  }
  if (!gw_setup(gw,gw.get_selected_nodes()))
      gw.deselect_all_nodes();
}

//----------------------------------------------------------------------------

void gw_setup_edge(GraphWin& gw, const point&) {
  edge e=gw.get_found_edge();
  if (e == nil) return;
  if (!gw.is_selected(e)) {
    gw.deselect_all_edges();
    gw.select(e);
  }
  if (!gw_setup(gw,gw.get_selected_edges()))
      gw.deselect_all_edges();
}

//----------------------------------------------------------------------------

static bool *edit_flush;

void gw_global_setup(GraphWin& gw, const point&) {
  gw.set_flush(*edit_flush);
  gw_global_setup(gw);
  *edit_flush=gw.set_flush(true);
}

//----------------------------------------------------------------------------

static double draw_grid(window& W, double xmin, double ymin, 
                                   double xmax, double ymax, 
                                   double x, double y)
{ 
  double d = W.pix_to_real(20);

  int nx = int((xmax - xmin)/d) + 1;
  int ny = int((ymax - ymin)/d) + 1;

  double x0 = x;
  while (x0 <= xmin) x0 += d;
  while (x0 >= xmin) x0 -= d;

  double y0 = y;
  while (y0 <= ymin) y0 += d;
  while (y0 >= ymin) y0 -= d;

  double p = W.pix_to_real(1);

  int N = (nx+2)*(ny+2);

  double* xc = new double[N];
  double* yc = new double[N];

  int n = 0;
  for(double i = x0; i < xmax; i += d)
    for(double j = y0; j < ymax; j += d)
    { if (n >= N) error_handler(1,"graphwin: error in draw_grid");
      xc[n] = i; yc[n] = j; n++;
     }

   W.draw_pixels(n,xc,yc);

   delete[] xc;
   delete[] yc;

   return d;
}



char*  GraphWin::graph_pixrect0(double& x0, double& y0, double x, double y,
                                                                  bool gr)
{ window& W = get_window();
  graph&  G = get_graph();

  double x1,y1;

  get_bounding_box(x0,y0,x1,y1);

  int win_w = W.width(); 
  int win_h = W.height(); 

  int w_pix = W.real_to_pix(x1-x0) + 10; 
  int h_pix = W.real_to_pix(y1-y0) + 10; 

  if (h_pix < win_h) h_pix = win_h + 8;

  if (w_pix > 2*win_w) w_pix = 2*win_w;
  if (h_pix > 2*win_h) h_pix = 2*win_h;


  double dx = W.xmin() - x0;
  double dy = W.ymax() - y1;

  double d = W.pix_to_real(8);

  double xx0 = x0 + dx;
  double yy0 = y0 + dy;
  double xx1 = x1 + dx;
  double yy1 = y1 + dy;

  W.start_buffering(w_pix,h_pix);
  W.draw_box(xx0-d,yy0-d,xx1+d,yy1+d,bg_color);

  if (gr && !get_grid_mode()) 
     draw_grid(W,xx0,yy0,xx1,yy1,x+dx,y+dy);

  draw_graph(dx,dy);
  char* pr = W.get_pixrect(xx0,yy0,xx1,yy1);
  W.stop_buffering();
  W.delete_buffer();

  return pr;
}


char*  GraphWin::graph_pixrect(double& x0, double& y0, double x, double y,
                                                                 bool gr)
{ window& W = get_window();
  graph&  G = get_graph();

  double x1,y1;

  get_bounding_box(x0,y0,x1,y1);

  int w_pix = 2 * W.width();
  int h_pix = 2 * W.height();

  if (W.real_to_pix(x1-x0) < w_pix && W.real_to_pix(y1-y0) < h_pix)
    return graph_pixrect0(x0,y0,x,y,gr);

  double dx = W.xmax() - x;
  double dy = W.ymin() - y;

  double xx0 = W.xreal(0);
  double yy0 = W.yreal(h_pix);
  double xx1 = W.xreal(w_pix);
  double yy1 = W.yreal(0);

  W.start_buffering(w_pix,h_pix);
  W.draw_box(xx0,yy0,xx1,yy1,bg_color);

  if (gr && !get_grid_mode()) 
     draw_grid(W,xx0,yy0,xx1,yy1,x+dx,y+dy);

  draw_graph(dx,dy);

  char* pr = W.get_pixrect(xx0,yy0,xx1,yy1);
  W.stop_buffering();
  W.delete_buffer();

  x0 = xx0 - dx;
  y0 = yy0 - dy;

  return pr;
}
  
  


void gw_scroll_graph(GraphWin& gw, const point& p, bool scroll = false) 
{
  window& W = gw.get_window();
  graph&  G = gw.get_graph();

  int N = G.number_of_nodes() + G.number_of_edges();

  bool nodes_only = N > gw.get_max_move_items();

  double xmin = W.xmin();
  double ymin = W.ymin();
  double xmax = W.xmax();
  double ymax = W.ymax();

  double x = p.xcoord();
  double y = p.ycoord();

  double d = W.pix_to_real(40);

  int dir = 0;

  if (fabs(ymax-y) < d || fabs(ymin-y) < d) dir = 1;
  if (fabs(xmax-x) < d || fabs(xmin-x) < d) dir = 2;

  unsigned long t = W.button_press_time();
  unsigned long t1;

  int event,val;

  window coord_win(105,18);

  coord_win.set_bg_color(ivory);

/*
  if (scroll)
    coord_win.display(W,2,W.ypix(ymin)-21);
*/


  double x0,y0,x1,y1;

  char* graph_pm = gw.graph_pixrect(x0,y0,x,y,scroll);

  W.start_buffering();
  W.clear();

  double dx = 0;
  double dy = 0;

  do { 
/*
       if (scroll)
       { string s("%+05.2f  %+05.2f",xmin-dx,ymin-dy);
         double tx = coord_win.xmax()/2;
         double ty = coord_win.ymax()/2;
         coord_win.clear();
         coord_win.draw_ctext(tx,ty,s,black);
        }
*/

       event = W.read_event(val,x1,y1,t1);

       if (event == motion_event && t1 - t < 50) continue;

       t  = t1;
       dx = x1-x;
       dy = y1-y;

       if (scroll)
       { if (dir == 1) dy = 0;
         if (dir == 2) dx = 0;
        }

       W.clear();

       if (scroll)
         { if (!gw.get_grid_mode()) 
            { draw_grid(W,xmin,ymin,xmax,ymax,x+dx,y+dy);
              W.put_pixrect(x0+dx,y0+dy,graph_pm);
             }
           else
            { W.put_pixrect(x0+dx,y0+dy,graph_pm);
              W.draw_grid();
             }
  
           if (dir == 0 || dir == 1)
           { W.draw_arrow(x+dx,y+dy,x+dx+d/2,y+dy);
             W.draw_arrow(x+dx,y+dy,x+dx-d/2,y+dy);
            }
    
           if (dir == 0 || dir == 2)
           { W.draw_arrow(x+dx,y+dy,x+dx,y+dy+d/2);
             W.draw_arrow(x+dx,y+dy,x+dx,y+dy-d/2);
            }
          }
       else
        { W.put_pixrect(x0+dx,y0+dy,graph_pm);
          if (!gw.get_grid_mode()) 
             draw_grid(W,xmin,ymin,xmax,ymax,0,0);
          else
             W.draw_grid();
        }

       W.flush_buffer();

  } while (event != button_release_event);

  W.del_pixrect(graph_pm);

  W.stop_buffering();

  if (scroll)
    gw.win_init(xmin-dx,xmax-dx,ymin-dy);
  else
  { bool b = gw.set_flush(false);
    node v;
    forall_nodes(v,G) gw.move_node(v,vector(dx,dy));
    edge e;
    forall_edges(e,G) gw.move_edge(e,vector(dx,dy));
    gw.embed_edges();
    gw.set_flush(b);
    gw.redraw();
   }
}


void gw_scroll_graph(GraphWin& gw, const point& p) 
{ gw_scroll_graph(gw,p,true); }



//----------------------------------------------------------------------------

void gw_select_area(GraphWin& gw, const point& p) 
{ window& W = gw.get_window();
  double x0 = p.xcoord();
  double y0 = p.ycoord();

  double x1,y1;
  gw.read_mouse_rect(x0,y0,x1,y1,true);

  list<node> area_nodes = gw.get_nodes_in_area(x0,y0,x1,y1);

  node v;
  bool select_all=false;
  forall(v,area_nodes) if (!gw.is_selected(v)) { select_all=true; break; } 

  bool old_flush=gw.set_flush(false);
  forall(v,area_nodes) gw.set_select(v,select_all);
  if (old_flush) gw.redraw();
  gw.set_flush(old_flush);

}

//----------------------------------------------------------------------------

void gw_split_edge(GraphWin& gw, const point& p) {
  edge e=gw.get_found_edge();
  if (e == nil) return;
  if (!gw.check_split_edge_handler(e)) return;
  window& W=gw.get_window();
  W.message("new node");
  color old_color=gw.set_color(e,blue);
  string s[]={ "split","leave","cancel" };
  int choice=W.read_vpanel("Split edge?",3,s);
  gw.set_color(e,old_color);
  W.del_messages();
  if (choice == 2) return;
  if (choice == 0) {
    node u=gw.new_node(p);
    node v=source(e);
    node w=target(e);
    gw.del_edge(e);
    gw.new_edge(v,u);
    gw.new_edge(u,w);
    gw.call_split_edge_handler(u);
  }
  else {
    if (gw.check_new_node_handler(p)) gw.call_new_node_handler(gw.new_node(p));
  }
}

//----------------------------------------------------------------------------


void gw_del_selected_nodes(GraphWin& gw, const point&) {
  node v=gw.get_found_node();
  if (v == nil) return;
  if (gw.is_selected(v)) {
    list<node> L(gw.get_selected_nodes());
    bool b = gw.set_flush(false);
    forall(v,L) { 
      if (gw.check_del_node_handler(v)) {
        gw.del_node(v);
        gw.call_del_node_handler();
      }
    }
   gw.redraw();
   gw.set_flush(b);
  }
}
  


//----------------------------------------------------------------------------

void GraphWin::set_action(long mask, gw_action f) {
  (*action)[mask]= f;
}

//----------------------------------------------------------------------------

void GraphWin::reset_actions() {

  if (action) delete action;

  action=new map<long,gw_action>(0);

  // left button (create,move,scroll,setup,zoom)

  set_action( A_LEFT                               , ::gw_new_node           );
  set_action( A_LEFT   |                    A_EDGE , ::gw_new_node           );
  set_action( A_LEFT   |                    A_NODE , ::gw_new_edge           );

  set_action( A_LEFT   | A_DRAG  |          A_NODE , ::gw_move_node          );
  set_action( A_LEFT   | A_DRAG  |          A_EDGE , ::gw_move_edge          );
  set_action( A_LEFT   | A_DRAG                    , ::gw_scroll_graph       );


  set_action( A_LEFT   | A_SHIFT | A_DRAG | A_NODE , ::gw_move_component     );
  set_action( A_LEFT   | A_DOUBLE| A_DRAG | A_NODE , ::gw_move_component     );

  set_action( A_LEFT   | A_SHIFT |          A_NODE , ::gw_setup_node ); 
  set_action( A_LEFT   | A_DOUBLE|          A_NODE , ::gw_setup_node );

  set_action( A_LEFT   | A_SHIFT |          A_EDGE , ::gw_setup_edge );
  set_action( A_LEFT   | A_DOUBLE|          A_EDGE , ::gw_setup_edge );

  set_action( A_LEFT   | A_SHIFT                   , ::gw_zoom_area );
  set_action( A_LEFT   | A_DOUBLE                  , ::gw_zoom_area );


  // middle button (selections)

  set_action( A_MIDDLE |                    A_NODE , ::gw_select_node        );
  set_action( A_MIDDLE |                    A_EDGE , ::gw_select_edge        );
  set_action( A_MIDDLE | A_DRAG                    , ::gw_select_area        );
  set_action( A_MIDDLE | A_SHIFT |          A_NODE , ::gw_select_component   );
  set_action( A_MIDDLE | A_DOUBLE|          A_NODE , ::gw_select_component   );
  set_action( A_MIDDLE | A_SHIFT                   , ::gw_select_all_nodes   );
  set_action( A_MIDDLE | A_DOUBLE                  , ::gw_select_all_nodes   );
  set_action( A_MIDDLE                             , ::gw_deselect_all_nodes );



  // right button (deletions)

  set_action( A_RIGHT  |                    A_NODE , ::gw_del_node           );
  set_action( A_RIGHT  |                    A_EDGE , ::gw_del_edge           );
  set_action( A_RIGHT  | A_SHIFT |          A_NODE , ::gw_del_component      );
  set_action( A_RIGHT  | A_DOUBLE|          A_NODE , ::gw_del_component      );


}

//----------------------------------------------------------------------------


int read_mouse(window& w, bool& double_click, bool& drag) 
{
  int timeout1 = 150;
  int timeout2 = 250;

  double x,y;
  int key,event,val;

  unsigned long t0 = w.button_press_time();
  unsigned long t,t1;

  drag = false;
  double_click = false;

  do { event = w.read_event(val,x,y,t1);
       if (t1-t0 > timeout1) break;
  } while (event != button_release_event);

  if (event != button_release_event)
     drag = true;
  else
    { double_click = ( t1-t0 < timeout1 && 
                      w.read_event(key,x,y,t,timeout2)==button_press_event );
      if (double_click)
      { do { event = w.read_event(val,x,y,t1);
             if (t1-t0 > timeout1) break;
            } while (event != button_release_event);
        drag = (event != button_release_event);
      }
    }

  return val;
}

//----------------------------------------------------------------------------

int GraphWin::edit() 
{
  int old_done_result=set_done_result(-1);

  double x,y;
  point p;

  int but;
  window *wp;
    
  bool old_flush =get_flush();

  edit_flush=&old_flush;

  for (;;) {

    set_flush(old_flush);

    int event;

    do { 
        event = read_event(wp,but,x,y);
        p = point(x,y);
/*
       if (event == motion_event && wp == win_p)
       { node u = found_node;
         node v = find_node(p);
         if (v != u) 
         { if (u) draw_node(u);
           if (v) draw_node(v);
          }
         edge x = found_edge;
         edge y = find_edge(p);
         if (x != y) 
         { if (x) draw_edge(x);
           if (y) draw_edge(y);
          }
       }
*/
      if (get_done_result() != -1) break;

    } while (event != button_press_event || wp != win_p);

    if (get_done_result() != -1) break;

    old_flush=set_flush(true);

    long MASK=0;

    switch (but) {
      case MOUSE_BUTTON(1) : MASK |= A_LEFT;   break;
      case MOUSE_BUTTON(2) : MASK |= A_MIDDLE; break;
      case MOUSE_BUTTON(3) : MASK |= A_RIGHT;  break;
    }

    if (win_p->shift_key_down()) MASK |= A_SHIFT;
    if (win_p->ctrl_key_down())  MASK |= A_CTRL;
    if (win_p->alt_key_down())   MASK |= A_ALT;

    bool dclick;
    bool drag;

    if ( but == NEW_BUTTON || but == INFO_BUTTON || but == DEL_BUTTON )
      read_mouse(*win_p,dclick,drag);

    if (drag)   MASK |= A_DRAG;
    if (dclick) MASK |= A_DOUBLE;

    if (find_node(p) != nil) 
       MASK |= A_NODE;
    else 
       if (find_edge(p) != nil) MASK |= A_EDGE;

    if (action->defined(MASK)) 
    { gw_action func = (*action)[MASK];
      if (func) func(*this,p);
     }

    if (status_win_open()) status_redraw(status_win);
   }

  return set_done_result(old_done_result);
}


//----------------------------------------------------------------------------

void GraphWin::remove_bends() {
  bool save_flush=set_flush(false);
  list<point> empty_list;
  edge e;
  forall_edges(e,get_graph()) 
     if (get_edge_points(e).size() != 2) set_bends(e,empty_list);
  set_flush(save_flush);
  if (get_flush()) redraw();
}

//----------------------------------------------------------------------------

void GraphWin::adjust_zoom_rect(double& x0, double& y0, double& x1, double& y1) 
{ 
  point p0(x0,y0);

  double wdx = get_xmax()-get_xmin();
  double wdy = get_ymax()-get_ymin();

  if (x0 > x1) wdy = -wdy;
  if (y0 > y1) wdx = -wdx;

  line L(p0,p0.translate(wdx,wdy));

  if (orientation(L,point(x1,y1)) < 0)
    y1 = L.y_proj(x1);
  else
    x1 = L.x_proj(y1);
}


static void adjust_zoom_rect1(GraphWin& gw, double& x0, double& y0, 
                                            double& x1, double& y1) 
{ gw.adjust_zoom_rect(x0,y0,x1,y1);
  point p0(x0,y0);
  point p1(x1,y1);
  point p2 = p1.reflect(p0);
  x0 = p2.xcoord();
  y0 = p2.ycoord();
  if (x0 > x1) { double tmp = x0; x0 = x1; x1 = tmp; } 
  if (y0 > y1) { double tmp = y0; y0 = y1; y1 = tmp; } 
}



void GraphWin::zoom_area(double x0, double y0, double x1, double y1)
{
  window& W = get_window();

  int pw = W.real_to_pix(x1-x0);
  int ph = W.real_to_pix(y1-y0);

  if (pw < 10 || ph < 10) 
  { redraw();
    return;
   }

  adjust_zoom_rect(x0,y0,x1,y1);

  if (x0 > x1) { double tmp = x0; x0 = x1; x1 = tmp; }
  if (y0 > y1) { double tmp = y0; y0 = y1; y1 = tmp; }

  if (get_flush())
  { int steps = get_animation_steps();

    double x00 = get_xmin();
    double x01 = get_xmax();
    double y00 = get_ymin();
    double y01 = get_ymax();

    zoom_x0 = x00;
    zoom_x1 = x01;
    zoom_y0 = y00;
    zoom_y1 = y01;

    double dx0 = (x0 - x00)/steps;
    double dx1 = (x1 - x01)/steps;
    double dy0 = (y0 - y00)/steps;

    while (steps--)
    { x00 += dx0;
      x01 += dx1;
      y00 += dy0;
      win_init(x00,x01,y00);
     }
  }


  win_init(x0,x1,y0);
}  


static void draw_zoom_rect(GraphWin& gw, double x0, double y0, 
                                         double x1, double y1) 
{ adjust_zoom_rect1(gw,x0,y0,x1,y1);
  gw.get_window().draw_rectangle(x0,y0,x1,y1);
}



void gw_zoom_area(GraphWin& gw, const point& p) 
{
  window& W = gw.get_window();

  double x0 = p.xcoord(); 
  double y0 = p.ycoord(); 

  gw.open_msg_win();
  char* msg = "ZOOM AREA    click left: zoom    drag middle: move    click right: cancel";
  gw.show_msg(msg);


  W.set_mode(xor_mode);

  double x1 = x0;
  double y1 = y0;

  bool canceled = false;

  W.draw_point(x0,y0);

  for(;;)
  { double x_new,y_new;
    int event,but;
    while ( (event = W.read_event(but,x_new,y_new)) != button_press_event)
    { if (event != motion_event) continue;
      draw_zoom_rect(gw,x0,y0,x_new,y_new);
      if (x1 != x0 || y1 != y0) 
        draw_zoom_rect(gw,x0,y0,x1,y1);
      x1 = x_new;
      y1 = y_new;
     }

    if (but == MOUSE_BUTTON(1)) break;

    if (but == MOUSE_BUTTON(3)) 
    { canceled = true;
      break;
     }

    if (but == MOUSE_BUTTON(2)) // move
    { double x_new,y_new;
      while (W.read_event(but,x_new,y_new) != button_release_event)
      { double dx = x1 - x_new;
        double dy = y1 - y_new;
        draw_zoom_rect(gw,x0-dx,y0-dy,x_new,y_new);
        W.draw_point(x0-dx,y0-dy);
        draw_zoom_rect(gw,x0,y0,x1,y1);
        W.draw_point(x0,y0);
        x0 -= dx;
        y0 -= dy;
        x1 = x_new;
        y1 = y_new;
       }
    }
  }

  W.set_mode(src_mode);

  gw.close_msg_win();

  if (canceled) 
  { gw.redraw();
    return;
  }

  bool b = gw.get_flush();
  adjust_zoom_rect1(gw,x0,y0,x1,y1);
  gw.zoom_area(x0,y0,x1,y1);
  gw.set_flush(b);
}





void GraphWin::unzoom() 
{ zoom_area(zoom_x0,zoom_y0,zoom_x1,zoom_y1); }  


void GraphWin::zoom(double f) 
{
  double xmin=get_xmin();
  double xmax=get_xmax();
  double ymin=get_ymin();
  double ymax=get_ymax();
  double xd=(xmax-xmin)*(1/f-1)/2;
  double yd=(ymax-ymin)*(1/f-1)/2;

  zoom_area(xmin-xd,ymin-yd,xmax+xd,ymax+yd);
}  




void GraphWin::fill_window()
{
  graph& G = get_graph();

  if (G.number_of_nodes() < 2) return;

  double xmin;
  double xmax;
  double ymin;
  double ymax;

  get_bounding_box(xmin,ymin,xmax,ymax);

  double dx = 0.1*(xmax - xmin);
  double dy = 0.1*(ymax - ymin);

  xmin -= dx;
  xmax += dx;
  ymin -= dy;
  ymax += dy;

  double x0 = xmin;
  double y0 = ymin;
  double x1 = xmax;
  double y1 = ymax;

  adjust_zoom_rect(x0,y0,x1,y1);

  dx = x1 - xmax; 
  dy = y1 - ymax; 

  zoom_area(x0-dx/2,y0-dy/2,x1-dx/2,y1-dy/2);
}

