
/******************************************************************************
* MODULE     : cursor.gen.cc
* DESCRIPTION: cursor handling
* COPYRIGHT  : (C) 1999  Joris van der Hoeven
*******************************************************************************
* This software falls under the GNU general public license and comes WITHOUT
* ANY WARRANTY WHATSOEVER. See the file $TEXMACS_PATH/LICENSE for more details.
* If you don't have this file, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/

#include <Interface/edit_cursor.gen.h>

#module code_edit_cursor
#import edit_cursor

/******************************************************************************
* Subroutines for cursor paths in trees
******************************************************************************/

bool
valid_cursor (tree t, path p, bool start_flag=FALSE) {
  if ((!nil (p)) && (!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] testing valid cursor " << p << " in " << t << "\n";
    fatal_error ("bad path", "valid_cursor", "edit_cursor.gen.cc");
  }

  if (nil (p)) return FALSE;
  if (atom (p)) {
    if (start_flag && (is_concat (t) || is_prime (t))) return (p->item!=0);
    if (is_child_enforcing (t)) return FALSE;
    return TRUE;
  }
  if (is_concat (t))
    return valid_cursor (t[p->item], p->next, start_flag || (p->item!=0));
  if (is_func (t, INACTIVE))
    return is_atomic (t[0]) || (!atom (p->next));
  if (is_prime (t)) return FALSE;
  return valid_cursor (t[p->item], p->next, FALSE);
}

path
pre_correct (tree t, path p) {
  if ((!nil (p)) && (!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] precorrecting " << p << " in " << t << "\n";
    fatal_error ("bad path", "pre_correct", "edit_cursor.gen.cc");
  }

  if (nil (p)) return pre_correct (t, path(0));
  if (atom (p)) {
    if (is_child_enforcing (t)) {
      if ((p->item==0) && is_accessible_child (t, 0))
	return path (0, pre_correct (t[0], path (0)));
      else {
	int l=N(t)-1;
	return path (l, pre_correct (t[l], path (right_index (t[l]))));
      }
    }
    return p;
  }
  if (is_func (t, INACTIVE) && is_compound (t[0]) && atom (p->next)) {
    t= t[0]; p= p->next;
    if (p->item==0) return path (0, path (0, pre_correct (t[0], path (0))));
    else {
      int l=N(t)-1;
      return path (0, path (l, pre_correct (t[l], path (right_index (t[l])))));
    }
  }
  if (is_prime (t)) {
    if (p->next->item == 0) return path (0);
    else return path (1);
  }
  return path (p->item, pre_correct (t[p->item], p->next));
}

bool
left_most (tree t, path p) {
  if (nil (p))
    fatal_error ("invalid nil path", "left_most", "edit_cursor.gen.cc");
  if ((!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] left most " << p << " in " << t << "\n";
    fatal_error ("bad path", "left_most", "edit_cursor.gen.cc");
  }

  int i=p->item;
  if (atom (p)) return i==0;
  if (is_concat (p)) return (i==0) && left_most (t[0], p->next);
  return FALSE;
}

path
left_correct (tree t, path p) {
  if (nil (p))
    fatal_error ("invalid nil path", "left_correct", "edit_cursor.gen.cc");
  if ((!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] left correcting " << p << " in " << t << "\n";
    fatal_error ("bad path", "left_correct", "edit_cursor.gen.cc");
  }

  int i=p->item;
  if (atom (p)) return p;
  if (is_concat (t) && (i>0) && left_most (t[i], p->next))
    return path (i-1, pre_correct (t[i-1], path (right_index (t[i-1]))));
  if (is_prime (t)) return path (0);
  return path (i, left_correct (t[i], p->next));
}

bool
right_most (tree t, path p) {
  if (nil (p))
    fatal_error ("invalid nil path", "right_most", "edit_cursor.gen.cc");
  if ((!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] right most " << p << " in " << t << "\n";
    fatal_error ("bad path", "right_most", "edit_cursor.gen.cc");
  }

  int i=p->item;
  if (atom (p)) return i==right_index (t);
  if (is_concat (p)) return (i==1) && right_most (t[N(t)-1], p->next);
  return FALSE;
}

path
right_correct (tree t, path p) {
  if (nil (p))
    fatal_error ("invalid nil path", "right_correct", "edit_cursor.gen.cc");
  if ((!atom (p)) && ((p->item < 0) || (p->item >= arity (t)))) {
    cerr << "TeXmacs] right correcting " << p << " in " << t << "\n";
    fatal_error ("bad path", "right_correct", "edit_cursor.gen.cc");
  }

  int i=p->item;
  if (atom (p)) return p;
  if (is_concat (t) && (i<N(t)-1) && right_most (t[i], p->next))
    return path (i+1, pre_correct (t[i-1], path (0)));
  if (is_prime (t)) return path (1);
  return path (i, right_correct (t[i], p->next));
}

/******************************************************************************
* Exported routines for cursor paths in trees
******************************************************************************/

path
correct_cursor (tree t, path p) {
  // cout << "Correct cursor " << p << " in " << t << "\n";
  return left_correct (t, pre_correct (t, p));
}

path
start (tree t, path p) {
  // cout << "Start " << p << " in " << t << "\n";
  if ((!nil (p)) && (arity (parent_subtree (t, p)) == 0)) return p;
  return correct_cursor (t, p * 0);
}

path
end (tree t, path p) {
  // cout << "End " << p << " in " << t << "\n";
  if ((!nil (p)) && (arity (parent_subtree (t, p)) == 0)) return p;
  return correct_cursor (t, p * right_index (subtree (t, p)));
}

path start (tree t) { return start (t, path ()); }
path end (tree t) { return end (t, path ()); }

path
up_correct (tree t, path p, bool active= TRUE) {
  if (nil (p)) return p;
  if ((p->item<0) || (p->item>=N(t))) return path ();
  if (active && (!is_accessible_child (t, p->item))) return path ();
  return path (p->item,
	       up_correct (t[p->item], p->next,
			   !is_func (t, INACTIVE, 1)));
}

path
super_correct (tree t, path p) {
  path q= path_up (p);
  path r= up_correct (t, q);
  if (q != r) {
    if ((!nil (r)) && is_atomic (subtree (t, r))) p= path_up (r) * 0;
    else p= r * 0;
  }
  return correct_cursor (t, p);
}

/******************************************************************************
* Constructor and destructor
******************************************************************************/

edit_cursor_rep::edit_cursor_rep ():
  cu (0, 0), mv (0, 0), mv_status (0) {}
edit_cursor_rep::~edit_cursor_rep () {}
cursor& edit_cursor_rep::the_cursor () { return cu; }
cursor& edit_cursor_rep::the_ghost_cursor () { return mv; }

/******************************************************************************
* Cursor movement
******************************************************************************/

#define DELTA (1<<23)

path
edit_cursor_rep::tree_path (SI x, SI y, SI delta) {
  return correct_cursor (et, eb->find_tree_path (x, y, delta));
}

bool
edit_cursor_rep::cursor_move_sub (SI& x0, SI& y0, SI& d0, SI dx, SI dy) {
  int i,d;
  path ref_p= tree_path (x0, y0, d0);
  if (ref_p != tp) {
    tp= ref_p;
    return TRUE;
  }
  
  // cout << "ref_p = " << ref_p << "\n";
  if (ref_p == tree_path (x0, y0, d0+ dx*DELTA)) {
    for (i=1; i<DELTA; i=i<<1)
      if (ref_p != tree_path (x0+ dx*i, y0+ dy*i, d0+ dx*DELTA)) break;
    if (i>=DELTA) return FALSE;
    for (d=i>>2; d>=1; d=d>>1)
      if (ref_p != tree_path (x0+ dx*(i-d), y0+ dy*(i-d), d0+ dx*DELTA)) i-=d;

    x0 += dx*i;
    y0 += dy*i;
  }
  
  // cout << "path  = " << tree_path (x0, y0, d0) << "\n";
  if (dx!=0) {
    if (ref_p == tree_path (x0, y0, d0)) {
      for (i=1; i<DELTA; i=i<<1)
	if (ref_p != tree_path (x0, y0, d0+ dx*i)) break;
      if (i>=DELTA)
	fatal_error ("inconsistent cursor handling",
		     "edit_cursor_rep::cursor_move_sub");
      for (d=i>>2; d>=1; d=d>>1)
	if (ref_p != tree_path (x0, y0, d0+ dx*(i-d))) i-=d;
      d0 += dx*i;
    }
    else {
      for (i=1; i<DELTA; i=i<<1)
	if (ref_p == tree_path (x0, y0, d0- dx*i)) break;
      if (i<DELTA) {
	for (d=i>>2; d>=1; d=d>>1)
	  if (ref_p == tree_path (x0, y0, d0- dx*(i-d))) i-=d;
	i--;
	d0 -= dx*i;
      }
      else {  // exceptional case
	ref_p= tree_path (x0, y0, d0- dx*DELTA);
	for (i=1; i<DELTA; i=i<<1)
	  if (ref_p == tree_path (x0, y0, d0- dx*i)) break;
	for (d=i>>2; d>=1; d=d>>1)
	  if (ref_p == tree_path (x0, y0, d0- dx*(i-d))) i-=d;
	d0 -= dx*i;
      }
    }
  }

  tp= tree_path (x0, y0, d0);
  return TRUE;
}

void
edit_cursor_rep::cursor_move (SI dx, SI dy) {
  cursor_move_sub (mv->ox, mv->oy, mv->delta, dx, dy);
}

/******************************************************************************
* Routines affecting both the cursor and the ghost cursor
******************************************************************************/

void
edit_cursor_rep::adjust_ghost_cursor (int status) {
  if (status==mv_status) {
    if (status!=HORIZONTAL) {
      mv->ox   = cu->ox;
      mv->delta= cu->delta;
    }
    if (status!=VERTICAL)
      mv->oy= cu->oy;
  }
}

void
edit_cursor_rep::notify_cursor_moved (int status) {
  mv_status= status;
  cu= eb->find_check_cursor (tp);
  notify_change (THE_CURSOR);
}

void
edit_cursor_rep::go_to (SI x, SI y) {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  tp= tree_path (x, y, 0);
  notify_cursor_moved (CENTER);
  mv->ox   = x;
  mv->oy   = y;
  mv->delta= 0;
}

void
edit_cursor_rep::go_left () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  adjust_ghost_cursor (VERTICAL);
  cursor_move (-1, 0);
  notify_cursor_moved (HORIZONTAL);
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_right () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  adjust_ghost_cursor (VERTICAL);
  cursor_move (1, 0);
  notify_cursor_moved (HORIZONTAL);
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_up () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  adjust_ghost_cursor (HORIZONTAL);
  cursor_move (0, 1);
  notify_cursor_moved (VERTICAL);
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_down () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  adjust_ghost_cursor (HORIZONTAL);
  cursor_move (0, -1);
  notify_cursor_moved (VERTICAL);
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_page_up () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  if (mv->oy >= eb->y2) return;
  go_to (mv->ox, mv->oy+ get_window_height ());
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_page_down () {
  if (has_changed (THE_TREE+THE_ENVIRONMENT)) return;
  if (mv->oy < eb->y1) return;
  go_to (mv->ox, mv->oy- get_window_height ());
  select_from_cursor_if_active ();
}

/******************************************************************************
* Logical cursor changes
******************************************************************************/

void
edit_cursor_rep::go_to (path p) {
  tp= p;
  mv_status= DIRECT;
  if (!has_changed (THE_TREE+THE_ENVIRONMENT)) {
    cu= eb->find_check_cursor (tp);
    mv= copy (cu);
  }
  notify_change (THE_CURSOR);
}

void
edit_cursor_rep::go_to_here () {
  cu= eb->find_check_cursor (tp);
  if (!cu->valid) {
    tp= super_correct (et, tp);
    cu= eb->find_check_cursor (tp);
  }
  if (mv_status == DIRECT) mv= copy (cu);
  notify_change (THE_CURSOR);
}

void
edit_cursor_rep::go_start () {
  go_to (correct_cursor (et, path (0)));
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_end () {
  go_to (correct_cursor (et, path (1)));
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_start_line () {
  path p= search_parent_upwards (DOCUMENT);
  go_to (start (et, p));
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_end_line () {
  path p= search_parent_upwards (DOCUMENT);
  go_to (end (et, p));
  select_from_cursor_if_active ();
}

void
edit_cursor_rep::go_start_of (string what) {
  path p= search_upwards (what);
  if (!nil (p)) go_to (start (et, p));
}

void
edit_cursor_rep::go_end_of (string what) {
  path p= search_upwards (what);
  if (!nil (p)) go_to (end (et, p));
}

void
edit_cursor_rep::go_start_with (string var, string val) {
  path p= search_upwards_with (var, val);
  if (!nil (p)) go_to (start (et, p));
}

void
edit_cursor_rep::go_end_with (string var, string val) {
  path p= search_upwards_with (var, val);
  if (!nil (p)) go_to (end (et, p));
}

/******************************************************************************
* Jumping to a label
******************************************************************************/

static void
get_labels_in (tree t, tree& u) {
  if (is_compound (t)) {
    if (is_func (t, LABEL, 1)) u << copy (t[0]);
    else {
      int i, n= N(t);
      for (i=0; i<n; i++)
	get_labels_in (t[i], u);
    }
  }
}

static path
search_tree_in (tree t, tree what) {
  if (is_atomic (t)) return path ();
  else if (t == what) return path (1);
  else {
    int i, n=N(t);
    for (i=0; i<n; i++) {
      path q= search_tree_in (t[i], what);
      if (!nil (q)) return path (i, q);
    }
    return path ();
  }
}

tree
edit_cursor_rep::get_labels () {
  tree r (TUPLE);
  get_labels_in (et, r);
  return r;
}

void
edit_cursor_rep::go_to_label (string s) {
  path p= search_tree_in (et, tree (LABEL, s));
  if (!nil (p)) go_to (p);
  else if (!nil (eb)) {
    p= eb->find_tag (s);
    if (!nil (p)) go_to (p);
  }
}

#endmodule // code_edit_cursor
