changeset 50177:297925dd73b1

New approach to scrolling and scroll bars for better redraw and smoother scroll bar behaviour.
author Jan Djärv <jan.h.d@swipnet.se>
date Mon, 17 Mar 2003 23:03:53 +0000
parents 45278f351181
children 61194aef8668
files src/ChangeLog src/gtkutil.c src/xterm.c
diffstat 3 files changed, 171 insertions(+), 107 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog	Mon Mar 17 18:56:53 2003 +0000
+++ b/src/ChangeLog	Mon Mar 17 23:03:53 2003 +0000
@@ -1,3 +1,27 @@
+2003-03-18  Jan Dj,Ad(Brv  <jan.h.d@swipnet.se>
+
+	* xterm.c (xg_scroll_callback): Remove xg_ignore_next_thumb.
+
+	* gtkutil.c (xg_initialize): Remove xg_ignore_next_thumb.
+
+2003-03-17  Jan Dj,Ad(Brv  <jan.h.d@swipnet.se>
+
+	* gtkutil.c: Removed handle_fixed_child, struct xg_last_sb_pos.
+	(xg_resize_widgets): Don't call foreach(handle_fixed_child).
+	(xg_gtk_scroll_destroy): Remove free of struct xg_last_sb_pos.
+	(scroll_bar_button_cb): Set bar->dragging to NIL on button release.
+	(xg_create_scroll_bar): Pass bar to button event callback.
+	(xg_find_top_left_in_fixed): New function.
+	(xg_update_scrollbar_pos): Don't call gdk_window_clear on
+	whole scroll bar area.  Get old position with
+	xg_find_top_left_in_fixed, calculate and only clear needed areas.
+	(xg_set_toolkit_scroll_bar_thumb): Do not adjust scroll bar if
+	dragging is in progress.  Calculate whole as for Motif.
+	Remove code that saved last values.  Call gtk_range functions to
+	set scroll bar sizes.
+
+	* gtkutil.h: Removed xg_ignore_next_thumb.
+
 2003-03-17  Juanma Barranquero  <lektu@terra.es>
 
 	* makefile.w32-in ($(BLD)/xdisp.$(O)): Add dependency on blockinput.h
--- a/src/gtkutil.c	Mon Mar 17 18:56:53 2003 +0000
+++ b/src/gtkutil.c	Mon Mar 17 23:03:53 2003 +0000
@@ -37,17 +37,6 @@
   (PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f))
 
 
-/* Key to save a struct xg_last_sb_pos in the scroll bars.  */
-#define XG_LAST_SB_POS "emacs_last_sb_pos"
-
-
-/*  Use this in scroll bars to keep track of when redraw is really needed.  */
-struct xg_last_sb_pos
-{
-  int portion, position, whole;
-};
-
-
 
 /***********************************************************************
                       Utility functions
@@ -298,24 +287,6 @@
   gdk_window_process_all_updates ();
 }
 
-/* When the Emacs frame is resized, we must call this function for each
-   child of our GtkFixed widget.  The children are scroll bars and
-   we invalidate the last position data here so it will be properly
-   redrawn later when xg_update_scrollbar_pos is called.
-   W is the child widget.
-   CLIENT_DATA is not used.  */
-static handle_fixed_child (w, client_data)
-     GtkWidget *w;
-     gpointer client_data;
-{
-  struct xg_last_sb_pos *last_pos
-    = g_object_get_data (G_OBJECT (w), XG_LAST_SB_POS);
-  if (last_pos)
-    {
-      last_pos->portion = last_pos->position = last_pos->whole = .1;
-    }
-}
-
 /* This gets called after the frame F has been cleared.  Since that is
    done with X calls, we need to redraw GTK widget (scroll bars).  */
 void
@@ -326,9 +297,6 @@
 
   if (wfixed)
     {
-      gtk_container_foreach (GTK_CONTAINER (wfixed),
-                             (GtkCallback) handle_fixed_child,
-                             NULL);
       gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE);
       gdk_window_process_all_updates ();
     }
@@ -2277,11 +2245,6 @@
    to indicate that callback should do nothing.  */
 int xg_ignore_gtk_scrollbar;
 
-/* After we send a scroll bar event,  x_set_toolkit_scroll_bar_thumb will
-   be called.  For some reason that needs to be debugged, it gets called
-   with bad values.  Thus, we set this variable to ignore those calls.  */
-int xg_ignore_next_thumb;
-
 /* SET_SCROLL_BAR_X_WINDOW assumes the second argument fits in
    32 bits.  But we want to store pointers, and they may be larger
    than 32 bits.  Keep a mapping from integer index to widget pointers
@@ -2391,16 +2354,15 @@
 
   p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_DATA);
   if (p) xfree (p);
-  p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_POS);
-  if (p) xfree (p);
   xg_remove_widget_from_map (id);
 }
 
 /* Callback for button press/release events.  Used to start timer so that
    the scroll bar repetition timer in GTK gets handeled.
+   Also, sets bar->dragging to Qnil when dragging (button release) is done.
    WIDGET is the scroll bar widget the event is for (not used).
    EVENT contains the event.
-   USER_DATA is 0 (not used).
+   USER_DATA points to the struct scrollbar structure.
 
    Returns FALSE to tell GTK that it shall continue propagate the event
    to widgets.  */
@@ -2412,9 +2374,13 @@
 {
   if (event->type == GDK_BUTTON_PRESS && ! xg_timer)
     xg_start_timer ();
-  else if (event->type == GDK_BUTTON_RELEASE && xg_timer)
-    xg_stop_timer ();
-
+  else if (event->type == GDK_BUTTON_RELEASE)
+    {
+      struct scroll_bar *bar = (struct scroll_bar *) user_data;
+      if (xg_timer) xg_stop_timer ();
+      bar->dragging = Qnil;
+    }
+  
   return FALSE;
 }
 
@@ -2460,28 +2426,18 @@
   g_signal_connect (G_OBJECT (wscroll),
                     "button-press-event",
                     G_CALLBACK (scroll_bar_button_cb),
-                    0);
+                    (gpointer)bar);
   g_signal_connect (G_OBJECT (wscroll),
                     "button-release-event",
                     G_CALLBACK (scroll_bar_button_cb),
-                    0);
+                    (gpointer)bar);
 
   gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget),
-                 wscroll, 0, 0);
+                 wscroll, -1, -1);
 
   /* Set the cursor to an arrow.  */
   xg_set_cursor (wscroll, &xg_left_ptr_cursor);
 
-  /* Allocate a place to hold the last scollbar size.  GTK redraw for
-     scroll bars is basically clear all, and then redraw.  This flickers
-     a lot since xg_update_scrollbar_pos gets called on every cursor move
-     and a lot more places.  So we have this to check if a redraw really
-     is needed.  */
-  struct xg_last_sb_pos *last_pos
-    = (struct xg_last_sb_pos *) xmalloc (sizeof (struct xg_last_sb_pos));
-  last_pos->portion = last_pos->position = last_pos->whole = -1;
-  g_object_set_data (G_OBJECT (wscroll), XG_LAST_SB_POS, last_pos);
-  
   SET_SCROLL_BAR_X_WINDOW (bar, scroll_id);
 }
 
@@ -2509,6 +2465,29 @@
     }
 }
 
+/* Find left/top for widget W in GtkFixed widget WFIXED.  */
+static void
+xg_find_top_left_in_fixed (w, wfixed, left, top)
+     GtkWidget *w, *wfixed;
+     int *left, *top;
+{
+  GList *iter;
+
+  for (iter = GTK_FIXED (wfixed)->children; iter; iter = g_list_next (iter))
+    {
+      GtkFixedChild *child = (GtkFixedChild *) iter->data;
+
+      if (child->widget == w)
+        {
+          *left = child->x;
+          *top = child->y;
+          return;
+        }
+    }
+
+  /* Shall never end up here.  */
+  abort ();
+}
 
 /* Update the position of the vertical scroll bar represented by SCROLLBAR_ID
    in frame F.
@@ -2525,34 +2504,101 @@
 {
 
   GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id);
-
+  
   if (wscroll)
     {
+      GtkWidget *wfixed = f->output_data.x->edit_widget;
       int gheight = max (height, 1);
-      struct xg_last_sb_pos *last_pos
-        = g_object_get_data (G_OBJECT (wscroll), XG_LAST_SB_POS);
-      GtkWidget *wfixed = f->output_data.x->edit_widget;
-
-      last_pos->portion = last_pos->position = last_pos->whole = -1;
-      xg_ignore_next_thumb = 0;
-
+      int canon_width = FRAME_SCROLL_BAR_COLS (f) * CANON_X_UNIT (f);
+      int winextra = canon_width > width ? (canon_width - width) / 2 : 0;
+      int bottom = top + gheight;
+
+      gint slider_width;
+      int oldtop, oldleft, oldbottom;
+      GtkRequisition req;
+      
+      /* Get old values.  */
+      xg_find_top_left_in_fixed (wscroll, wfixed, &oldleft, &oldtop);
+      gtk_widget_size_request (wscroll, &req);
+      oldbottom = oldtop + req.height;
+
+      /* Scroll bars in GTK has a fixed width, so if we say width 16, it
+         will only be its fixed width (14 is default) anyway, the rest is
+         blank.  We are drawing the mode line across scroll bars when
+         the frame is split:
+                               |bar| |fringe|
+                              ----------------
+                              mode line
+                              ----------------
+                               |bar| |fringe|
+
+         When we "unsplit" the frame:
+
+                               |bar| |fringe|
+                              -|   |-|      |
+                              m¦   |i|      |
+                              -|   |-|      |
+                               |   | |      |
+
+
+         the remains of the mode line can be seen in these blank spaces.
+         So we must clear them explicitly.
+         GTK scroll bars should do that, but they don't.
+         Also, the scroll bar canonical width may be wider than the width
+         passed in here.  */
+
+      if (oldtop != -1 && oldleft != -1)
+        {
+          int gtkextra;
+          int xl, xr, wblank;
+          int bottomdiff, topdiff;
+
+          gtk_widget_style_get (wscroll, "slider_width", &slider_width, NULL);
+          gtkextra = width > slider_width ? (width - slider_width) / 2 : 0;
+        
+          xl = left - winextra;
+          wblank = gtkextra + winextra;
+          xr = left + gtkextra + slider_width;
+          bottomdiff = abs (oldbottom - bottom);
+          topdiff = abs (oldtop - top);
+
+          if (oldtop > top)
+            {
+              gdk_window_clear_area (wfixed->window, xl, top, wblank, topdiff);
+              gdk_window_clear_area (wfixed->window, xr, top, wblank, topdiff);
+            }
+          else if (oldtop < top)
+            {
+              gdk_window_clear_area (wfixed->window, xl, oldtop, wblank,
+                                     topdiff);
+              gdk_window_clear_area (wfixed->window, xr, oldtop, wblank,
+                                     topdiff);
+            }
+
+          if (oldbottom > bottom)
+            {
+              gdk_window_clear_area (wfixed->window, xl, bottom, wblank,
+                                     bottomdiff);
+              gdk_window_clear_area (wfixed->window, xr, bottom, wblank,
+                                     bottomdiff);
+            }
+          else if (oldbottom < bottom)
+            {
+              gdk_window_clear_area (wfixed->window, xl, oldbottom, wblank,
+                                     bottomdiff);
+              gdk_window_clear_area (wfixed->window, xr, oldbottom, wblank,
+                                     bottomdiff);
+            }
+        }
+
+      /* Move and resize to new values.  */
       gtk_fixed_move (GTK_FIXED (wfixed), wscroll, left, top);
       gtk_widget_set_size_request (wscroll, width, gheight);
 
       gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE);
 
-      /* Must force out update so wscroll gets the resize.
-         Otherwise, the gdk_window_clear clears the old window size.  */
-      gdk_window_process_all_updates ();
-
-      /* The scroll bar doesn't explicitly redraw the whole window
-         when a resize occurs.  Since the scroll bar seems to be fixed
-         in width it doesn't fill the space reserved, so we must clear
-         the whole window.  */
-      gdk_window_clear (wscroll->window);
-
-      /* Since we are not using a pure gtk event loop, we must force out
-         pending update events with this call.  */
+      /* Make GTK draw the new sizes.  We are not using a pure GTK event
+         loop so we need to do this.  */
       gdk_window_process_all_updates ();
 
       SET_FRAME_GARBAGED (f);
@@ -2570,16 +2616,10 @@
   GtkWidget *wscroll = xg_get_widget_from_map (SCROLL_BAR_X_WINDOW (bar));
 
   FRAME_PTR f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window)));
-  struct xg_last_sb_pos *last_pos
-    = (wscroll ? g_object_get_data (G_OBJECT (wscroll), XG_LAST_SB_POS) : 0);
 
   BLOCK_INPUT;
-  if (wscroll
-      && ! xg_ignore_next_thumb
-      && last_pos
-      && (last_pos->portion != portion
-          || last_pos->position != position
-          || last_pos->whole != whole))
+
+  if (wscroll && NILP (bar->dragging))
     {
       GtkAdjustment *adj;
       gdouble shown;
@@ -2588,6 +2628,14 @@
 
       adj = gtk_range_get_adjustment (GTK_RANGE (wscroll));
 
+      /* We do the same as for MOTIF in xterm.c, assume 30 chars per line
+         rather than the real portion value.  This makes the thumb less likely
+         to resize and that looks better.  */
+      portion = XFASTINT (XWINDOW (bar->window)->height) * 30;
+      /* When the thumb is at the bottom, position == whole.
+         So we need to increase `whole' to make space for the thumb.  */
+      whole += portion;
+
       if (whole <= 0)
         top = 0, shown = 1;
       else
@@ -2604,34 +2652,28 @@
       value = min (value, whole - size);
       value = max (value, XG_SB_MIN);
 
-      adj->upper = max (whole, size);
       adj->page_size = (int)size;
 
-      /* Assume a page increment is about 95% of the page size  */
-      adj->page_increment = (int) (0.95*adj->page_size);
-
-      /* Assume all lines are equal.  */
-      adj->step_increment = portion / max (1, FRAME_HEIGHT (f));
-
-      if (last_pos)
-        {
-          last_pos->portion = portion;
-          last_pos->position = position;
-          last_pos->whole = whole;
-        }
-
       /* gtk_range_set_value invokes the callback.  Set
          ignore_gtk_scrollbar to make the callback do nothing  */
       xg_ignore_gtk_scrollbar = 1;
+
+      gtk_range_set_range (GTK_RANGE (wscroll), adj->lower, max (whole, size));
+
+      /* Assume all lines are of equal size.  */
+      /* Assume a page increment is about 95% of the page size  */
+      gtk_range_set_increments (GTK_RANGE (wscroll),
+                                portion / max (1, FRAME_HEIGHT (f)),
+                                (int) (0.95*adj->page_size));
+
       gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value);
       xg_ignore_gtk_scrollbar = 0;
 
-      /* Make sure the scroll bar is redrawn with new thumb  */
-      gtk_widget_queue_draw (wscroll);
+      /* Make GTK draw the new thumb.  We are not using a pure GTK event
+         loop so we need to do this.  */
+      gdk_window_process_all_updates ();
     }
 
-  gdk_window_process_all_updates ();
-  xg_ignore_next_thumb = 0;
   UNBLOCK_INPUT;
 }
 
@@ -3079,7 +3121,6 @@
 xg_initialize ()
 {
   xg_ignore_gtk_scrollbar = 0;
-  xg_ignore_next_thumb = 0;
   xg_left_ptr_cursor = 0;
   xg_did_tearoff = 0;
 
--- a/src/xterm.c	Mon Mar 17 18:56:53 2003 +0000
+++ b/src/xterm.c	Mon Mar 17 23:03:53 2003 +0000
@@ -6395,7 +6395,7 @@
 
 #else /* !USE_MOTIF, i.e. Xaw or GTK */
 #ifdef USE_GTK
-/* Scroll bar callback for Gtk scroll bars.  WIDGET is the scroll
+/* Scroll bar callback for GTK scroll bars.  WIDGET is the scroll
    bar adjustment widget.  DATA is a pointer to the scroll_bar structure. */
 
 static void
@@ -6453,13 +6453,12 @@
     {
       part = scroll_bar_handle;
       whole = adj->upper - adj->page_size;
-      portion = min (position, whole);
-      bar->dragging = make_number (portion);
+      portion = min ((int)position, whole);
+      bar->dragging = make_number ((int)portion);
     }
 
   if (part >= 0)
     {
-      xg_ignore_next_thumb = 1;
       window_being_scrolled = bar->window;
       last_scroll_bar_part = part;
       x_send_scroll_bar_event (bar->window, part, portion, whole);