#ifndef lint
#ifdef sccs
static  char sccsid[] = "@(#)scrollbar_event.c 1.1 94/10/31 Copyr 1986 Sun Micro";
#endif
#endif

/*************************************************************************/
/*                         scrollbar_event.c                             */
/*           Copyright (c) 1985 by Sun Microsystems, Inc.                */
/*************************************************************************/

#include <suntool/scrollbar_impl.h> 

#define FORWARD_BUTTON		1
#define BACKWARD_BUTTON		2
#define NO_BUTTON 		3

static void		scrollbar_resize();
static void		post_scroll();
static void		compute_new_view_start();
static int		find_region();
static void		set_cursor_from_mouse();
static Notify_value	scrollbar_timer_proc();
static void		scrollbar_timer_start();
static void		scrollbar_timer_stop();

/**************************************************************************/
/* cursors to cache cursor images                                         */
/**************************************************************************/

extern	struct cursor scrollbar_client_saved_cursor;

/**************************************************************************/
/* timer declarations                                                     */
/**************************************************************************/

struct itimerval scrollbar_timer;
int		 scrollbar_t_on;
Event            scrollbar_t_event;
Scroll_motion    scrollbar_t_motion;

/**************************************************************************/
/* scrollbar_event                                                        */
/**************************************************************************/

static short button_active_cursor_image[16] = {
/* black diamond */
        0x0000,0x0000,0x0000,0x0180,0x03C0,0x07E0,0x0FF0,0x1FF8,
        0x1FF8,0x0FF0,0x07E0,0x03C0,0x0180,0x0000,0x0000,0x0000
};
mpr_static(scrollbar_button_active_mpr, 16, 16, 1, button_active_cursor_image);
struct cursor scrollbar_button_active_cursor = 
   {5,7,PIX_SRC|PIX_DST,&scrollbar_button_active_mpr};


/*ARGSUSED*/
Notify_value
scrollbar_event(sb, event, arg, type)
scrollbar_handle   sb;
Event             *event;
caddr_t		   arg;
Notify_event_type  type;
{
   register Scroll_motion   motion;
   struct cursor           *cursor = NULL;
   struct cursor           *active_cursor;
   int                      undoing = FALSE;
   
   if (find_region(sb, event)==NO_BUTTON)
      active_cursor = sb->active_cursor;
   else
      active_cursor = &scrollbar_button_active_cursor;

   switch (event_action(event)) {
          
      case WIN_REPAINT:
         (void)scrollbar_paint_clear((Scrollbar)(LINT_CAST(sb)));
         return NOTIFY_DONE;
          
      case WIN_RESIZE:
         scrollbar_resize((Scrollbar)(LINT_CAST(sb)));
         return NOTIFY_DONE;
        
      case LOC_RGNENTER:
         /* Always fetch client's cursor */
	 (void)win_getcursor(sb->client_windowfd, &scrollbar_client_saved_cursor);

         set_cursor_from_mouse(sb, active_cursor);

         (void)scrollbar_repaint((Scrollbar)(LINT_CAST(sb)), SCROLLBAR_INSIDE);
         scrollbar_active_sb = sb;
	 (void)win_post_id_and_arg(sb->notify_client, SCROLL_ENTER, NOTIFY_SAFE, 
			     (char *)sb, NOTIFY_COPY_NULL, NOTIFY_RELEASE_NULL);
         return NOTIFY_DONE;

      case LOC_RGNEXIT:
         /* Always restore client's cursor */
	 (void)win_setcursor(sb->client_windowfd, &scrollbar_client_saved_cursor);
         (void)scrollbar_repaint((Scrollbar)(LINT_CAST(sb)), SCROLLBAR_OUTSIDE);
         scrollbar_active_sb = NULL;
	 (void)win_post_id_and_arg(sb->notify_client, SCROLL_EXIT, NOTIFY_SAFE,
	 		(char *)sb, NOTIFY_COPY_NULL, NOTIFY_RELEASE_NULL);
         scrollbar_timer_stop(sb);
         return NOTIFY_DONE;
       
      case LOC_MOVE:
         if (scrollbar_t_on) {
            scrollbar_t_event.ie_locx = event->ie_locx;
            scrollbar_t_event.ie_locy = event->ie_locy;
         } else
            set_cursor_from_mouse(sb, active_cursor);
         return NOTIFY_DONE;
       
      case KBD_REQUEST:
	 /* Scrollbar always refuses kbd focus request */
	 (void)win_refuse_kbd_focus(sb->client_windowfd);
         scrollbar_timer_stop(sb);
         return NOTIFY_DONE;
       
      case MS_LEFT:
         cursor = sb->forward_cursor;
         switch (find_region(sb, event)) {
            case FORWARD_BUTTON:
               motion = SCROLL_LINE_FORWARD;
               break;
            case BACKWARD_BUTTON:
               motion = SCROLL_LINE_FORWARD;
               break;
            case NO_BUTTON:
               motion = event_shift_is_down(event) ?
                        SCROLL_MAX_TO_POINT : SCROLL_POINT_TO_MIN;
               break;
         }
         break;
         
      case MS_MIDDLE:
         cursor = sb->absolute_cursor;
         switch (find_region(sb, event)) {
            case FORWARD_BUTTON:
               motion = event_shift_is_down(event) ?
                        SCROLL_PAGE_BACKWARD : SCROLL_PAGE_FORWARD;
               break;
            case BACKWARD_BUTTON:
               motion = event_shift_is_down(event) ?
                        SCROLL_PAGE_BACKWARD : SCROLL_PAGE_FORWARD;
               break;
            case NO_BUTTON:
               motion = SCROLL_ABSOLUTE;
               undoing = event_shift_is_down(event);
               break;
         }
         break;
         
      case MS_RIGHT:
         cursor = sb->backward_cursor;
         switch (find_region(sb, event)) {
            case FORWARD_BUTTON:
               motion = SCROLL_LINE_BACKWARD;
               break;
            case BACKWARD_BUTTON:
               motion = SCROLL_LINE_BACKWARD;
               break;
            case NO_BUTTON:
               motion = event_shift_is_down(event) ?
                        SCROLL_POINT_TO_MAX : SCROLL_MIN_TO_POINT;
               break;
         }
         break;

      case ACTION_HELP:
	 if (event_is_down(event))
	    help_request((caddr_t)(LINT_CAST(sb->notify_client)),
			 sb->help_data, event);
         return NOTIFY_DONE;

#ifdef notdef
      case KEY_LEFT(4):	/* UNDO */
         if (event_is_up(event))
            post_scroll(sb, event, SCROLL_ABSOLUTE, TRUE);
         return NOTIFY_DONE;
#endif              

      default:  {
	 Rect	rect;
	
	 /* Translate mouse x and y so that movements inside
 	  * the scrollbar are posted with co-ordinates relative
	  * to the window that encloses the scrollbar. 
	  */
	 rect = sb->rect;
 	 event->ie_locx += rect.r_left;
         event->ie_locy += rect.r_top;

 	 (void)win_post_id_and_arg(sb->notify_client, event_id(event), NOTIFY_SAFE, 
			     (char *)sb, NOTIFY_COPY_NULL, NOTIFY_RELEASE_NULL);

	 return NOTIFY_DONE;
	}
   }

   if (win_inputposevent(event)) {
      scrollbar_timer_start(sb, event, motion);
   } else {
      if (!scrollbar_t_on)
         post_scroll(sb, event, motion, undoing);
      cursor = active_cursor;
      scrollbar_timer_stop(sb);
   }

   if (cursor)
      (void)win_setcursor(sb->client_windowfd, cursor);

   return NOTIFY_DONE;
}

static 
find_region(sb, event)
scrollbar_handle   sb;
Event             *event;
{
   register short loc;
   register int   forward_button_limit;
   register int   backward_button_limit;

   if (!sb->buttons)
      return NO_BUTTON;

   if (sb->horizontal) {
      loc = event_x(event);
      forward_button_limit  = sb->button_length;
      backward_button_limit = sb->rect.r_width - sb->button_length;
   } else {
      loc = event_y(event);
      forward_button_limit  = sb->button_length;
      backward_button_limit = sb->rect.r_height - sb->button_length;
   }

   if (loc < forward_button_limit)
      return FORWARD_BUTTON; 
   else if (loc > backward_button_limit)
      return BACKWARD_BUTTON; 
   else
      return NO_BUTTON;
}

static void
set_cursor_from_mouse(sb, active_cursor)
scrollbar_handle	sb;
struct cursor		*active_cursor;
{
   register int	fd = sb->client_windowfd;

   /* figure which cursor to install from mouse state */
   if (win_get_vuid_value(fd, MS_LEFT))
      active_cursor = sb->forward_cursor;
   else if (win_get_vuid_value(fd, MS_MIDDLE))
      active_cursor = sb->absolute_cursor;
   else if (win_get_vuid_value(fd, MS_RIGHT))
      active_cursor = sb->backward_cursor;
   /* ..else keep active_cursor unchanged */

   /* set cursor if not NULL */
   if (active_cursor)
      (void)win_setcursor(fd, active_cursor);
}

/**************************************************************************/
/* post_scroll                                                            */
/* undoing policy: undo to the last position which was departed from      */
/* via an "undoable" motion.  Undoable motions are: thumbing, undoing.    */
/**************************************************************************/

static void
post_scroll(sb, event, motion, undoing)
scrollbar_handle         sb;
Event	                *event;
Scroll_motion            motion;
int                      undoing;
{
   register int bar_length, request_offset, old_request_offset;

   /* compute bar length, offset of request into bar (incl. page buttons) */
   old_request_offset = sb->request_offset;
   if (sb->horizontal) {
      request_offset = event_x(event);
      bar_length = sb->rect.r_width;
   } else {
      request_offset = event_y(event);
      bar_length = sb->rect.r_height;
   }

   /* if thumbing, save request_offset and view_start for future undo */
   if (!undoing && motion == SCROLL_ABSOLUTE) {
      sb->undo_request_offset = sb->request_offset;
      sb->undo_mark           = sb->view_start;

      /* don't scroll if double-thumbing in same spot! */
      if (sb->request_motion==SCROLL_ABSOLUTE
      && request_offset==old_request_offset)
         return;
   }

   sb->request_motion = motion;
   sb->request_offset = request_offset;

   /* if undoing, restore view_start from undo_mark, else compute it */
   /* First save view_start as old_view_start.  When if the newly    */
   /* computed view_start is different, set the bubble_modified flag */
   /* so that the next scrollbar_paint erases the old bubble.        */
   sb->old_view_start = sb->view_start;
   if (undoing) {
      sb->request_offset      = sb->undo_request_offset;
      sb->view_start          = sb->undo_mark;
      sb->undo_mark           = sb->old_view_start;
      sb->undo_request_offset = old_request_offset;
   } else
      compute_new_view_start(sb, bar_length);
   if (sb->view_start != sb->old_view_start)
      sb->bubble_modified = TRUE;

   (void)win_post_id_and_arg(sb->notify_client, SCROLL_REQUEST, NOTIFY_SAFE, 
   		       (char *)sb, NOTIFY_COPY_NULL, NOTIFY_RELEASE_NULL);
}

/**************************************************************************/
/* compute_new_view_start                                                 */
/* In the relative motions, we take into account that the scrollbar's     */
/* view_start, view_length and object_length are in client units, which   */
/* may be other than pixels -- chars or lines, for example.               */
/**************************************************************************/

static void
compute_new_view_start(sb, bar_length)
register scrollbar_handle	sb;
int				bar_length;
{
   int	align_to_max	= FALSE;
   int	scrolling_up	= TRUE;
   int	desired_scroll	= 0;
   int	new_offset 	= sb->view_start; /*note use of int, not unsigned*/
   int	min_offset 	= 0;
   int	max_offset 	= sb->object_length;
   int	margin_offset 	= sb->normalize_margin;
   int	gap_offset 	= sb->gap;
   int	line_offset 	= sb->line_height;

   /* If gap not yet set, use margin. */
   if (gap_offset==SCROLLBAR_INVALID_LENGTH)
      gap_offset = margin_offset;

   switch (sb->request_motion) {

      case SCROLL_ABSOLUTE: {
	 /* factor button lengths out; save bar_length for normalization */
	 int  offset_into_bar = sb->request_offset - sb->button_length;
	 int  bar_size        = bar_length - 2*sb->button_length;
	 int  end_point_area  = sb->end_point_area;
	 int  bubble_top_offset;
	 int  bubble_extent;
	 struct rect   bubble;

	 if (end_point_area > bar_size/2)
	    end_point_area = bar_size/2;
	 
	 /* adjust for top/bottom gravity */
	 if (offset_into_bar <= end_point_area) {
	    new_offset = 0;
	    break;
	 } else if (bar_size - offset_into_bar <= end_point_area) {
	    new_offset = max_offset - (3*(sb->view_length/4));
	 /* desired_scroll = -(sb->line_height); */
	    break;
	 }

	 /* figure where the top of the bubble should go */
	 (void)scrollbar_compute_bubble_rect(sb, &bubble);
	 if (sb->horizontal)
	    bubble_extent = bubble.r_width;
	 else
	    bubble_extent = bubble.r_height;
	 bubble_top_offset = offset_into_bar - (bubble_extent / 2);
	 if (bubble_top_offset < 0)
	    bubble_top_offset = 0;

	 /* finally, compute the new view_start */
	 new_offset = ((double)sb->object_length * bubble_top_offset) /
                       bar_size;
	 break;
      }

      case SCROLL_FORWARD: /* POINT_TO_MIN */
	 desired_scroll = sb->request_offset;
	 break;

      case SCROLL_BACKWARD: /* MIN_TO_POINT */
	 desired_scroll = -(sb->request_offset);
	 scrolling_up = FALSE; 
	 break;

      case SCROLL_MAX_TO_POINT:
         if (!sb->advanced_mode) {
	    sb->request_offset = bar_length - sb->request_offset;
	    sb->request_motion = SCROLL_FORWARD;
         }
	 desired_scroll = bar_length - sb->request_offset;
	 align_to_max = TRUE;
	 break;

      case SCROLL_POINT_TO_MAX:
         if (!sb->advanced_mode) {
	    sb->request_offset = bar_length - sb->request_offset;
	    sb->request_motion = SCROLL_BACKWARD;
         }
	 desired_scroll = -(bar_length - sb->request_offset);
	 align_to_max = TRUE;
	 scrolling_up = FALSE; 
	 break;

      case SCROLL_PAGE_FORWARD:
         if (!sb->advanced_mode) {
	    sb->request_offset = bar_length;
	    sb->request_motion = SCROLL_FORWARD;
         }
	 new_offset += sb->view_length;
	 break;

      case SCROLL_PAGE_BACKWARD:
         if (!sb->advanced_mode) {
	    sb->request_offset = bar_length;
	    sb->request_motion = SCROLL_BACKWARD;
         }
	 new_offset -= sb->view_length;
	 scrolling_up = FALSE; 
	 break;

      case SCROLL_LINE_FORWARD:
	 desired_scroll = sb->line_height;
	 if (sb->use_grid && !new_offset) /* special case for top of object */
	    desired_scroll += (margin_offset - gap_offset);
         if (!sb->advanced_mode) {
	    sb->request_offset = desired_scroll;
	    sb->request_motion = SCROLL_FORWARD;
         }
	 break;

      case SCROLL_LINE_BACKWARD:
         if (!sb->advanced_mode) {
	    sb->request_offset = sb->line_height;
	    sb->request_motion = SCROLL_BACKWARD;
         }
	 desired_scroll = -(sb->line_height);
	 scrolling_up = FALSE; 
	 break;
   }
   if (desired_scroll)	/* int cast rqd due to c's brain damaged mult! */
      new_offset += (desired_scroll * (int)sb->view_length) / bar_length;
/*
 * Grid Normalization:
 *
 *	+---------------------------------------+ margin_offset
 *	+---------------------------------------+ -------
 *	|					|    |
 *	|					| line_offset
 *	|					|    |
 *	|.......................................| -------
 *	|					|
 *	|					|
 *------|---------------------------------------|---- new_offset
 *	|.......................................|
 *	|					|
 *	|					|
 *
 *
 *  We assume the view space is regularly divided by cells of equal size
 *  (line_height) with an initial gap and external margin, both measured
 *  in pixels.  Note that the line_height is assumed to contain the
 *  inter-object white space gap.  This is because the line ht is used
 *  as the amount to move on a line up/down scroll.
 *
 *  Normalization simply "rounds" the new_offset to be at an integral
 *  number of line_heights from the start of the view.  The "rounding
 *  rule" is to round up to the next line when scrolling down,
 *  and round down when scrolling up.  This preserves partial objects:
 *  A motion to a partial object will not inadvertantly scroll it out
 *  of the view window.
 *
 *  Fine point:  Note that we do not subtract margin from the new_offset
 *  when calculating the number of lines new_offset corresponds to.  This
 *  is so that a point within margin of line "n" will show that line,
 *  not line n-1.
 */
   if (/*sb->line_height &&*/ sb->use_grid) {
      if (sb->view_length != bar_length) {
         margin_offset = (margin_offset * sb->view_length) / bar_length;
         line_offset   = (line_offset   * sb->view_length) / bar_length;
         gap_offset    = (gap_offset    * sb->view_length) / bar_length;
      }

      if (line_offset) {
         min_offset = margin_offset;
         max_offset = (max_offset - margin_offset) / line_offset;
         max_offset = (max_offset * line_offset) + margin_offset - gap_offset;
         if (align_to_max)
            new_offset += sb->view_length;
         if (!scrolling_up)
            new_offset += line_offset - 1;
         new_offset = (new_offset - margin_offset + gap_offset) / line_offset;
         new_offset = (new_offset * line_offset) + margin_offset - gap_offset;
         if (align_to_max)
            new_offset -= sb->view_length;
      }

#ifdef notdef
      if (line_offset) {
         min_offset = margin_offset;
         max_offset = ((max_offset-margin_offset)/line_offset) * line_offset;
         if (align_to_max)
            new_offset += sb->view_length;
         if (!scrolling_up)
            new_offset += line_offset - 1;
         new_offset = (new_offset /*-margin*/ / line_offset) * line_offset;
         if (align_to_max)
            new_offset -= sb->view_length;
      }
#endif
   }

   if (new_offset <= min_offset)
      new_offset = 0;
   else if (new_offset > max_offset)
      new_offset = max_offset;

   sb->view_start = new_offset;
}
/**************************************************************************/
/* scrollbar_resize                                                       */
/**************************************************************************/

/*ARGSUSED*/
static void
scrollbar_resize(sb)
scrollbar_handle sb;
{
}

/************************************************************************/
/* timer notify proc                                                    */
/************************************************************************/

/*ARGSUSED*/
static Notify_value
scrollbar_timed_out(client, which)
Notify_client client;
int           which;
{
   scrollbar_t_on  = TRUE;
   post_scroll((scrollbar_handle)(LINT_CAST(client)), &scrollbar_t_event,
                scrollbar_t_motion, FALSE);
   return NOTIFY_DONE;
}

static void
scrollbar_timer_start(sb, event, motion)
scrollbar_handle	sb;
Event			*event;
Scroll_motion		motion;
{
   int		tenths, usecs, secs;
   
   tenths = sb->delay;
   if (!tenths)
       return;

   usecs  = (tenths % 10)*100000;
   secs   = tenths / 10;

   scrollbar_timer.it_value.tv_usec = usecs;
   scrollbar_timer.it_value.tv_sec  = secs;
   scrollbar_timer.it_interval.tv_usec = 1;
   scrollbar_timer.it_interval.tv_sec  = 0;

   scrollbar_t_event  = *event;
   scrollbar_t_motion = motion;

   (void)notify_set_itimer_func((Notify_client)sb, scrollbar_timed_out,
      ITIMER_REAL, &scrollbar_timer, (struct itimerval *)NULL);
}

static void
scrollbar_timer_stop(sb)
scrollbar_handle	sb;
{
   scrollbar_t_on  = FALSE;
   (void)notify_set_itimer_func((Notify_client)sb, scrollbar_timed_out, 
   	ITIMER_REAL, (struct itimerval *)NULL, (struct itimerval *)NULL);
}

