XEmacs 21.4.17 "Jumbo Shrimp".
[chise/xemacs-chise.git.1] / src / scrollbar-gtk.c
1 /* scrollbar implementation -- GTK interface.
2    Copyright (C) 1994, 1995 Board of Trustees, University of Illinois.
3    Copyright (C) 1994 Amdhal Corporation.
4    Copyright (C) 1995 Sun Microsystems, Inc.
5    Copyright (C) 1995 Darrell Kindred <dkindred+@cmu.edu>.
6
7 This file is part of XEmacs.
8
9 XEmacs is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by the
11 Free Software Foundation; either version 2, or (at your option) any
12 later version.
13
14 XEmacs is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with XEmacs; see the file COPYING.  If not, write to
21 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.  */
23
24 /* Synched up with: Not in FSF. */
25 /* Gtk version by William M. Perry */
26
27 #include <config.h>
28 #include "lisp.h"
29
30 #include "console-gtk.h"
31 #include "glyphs-gtk.h"
32 #include "gui-gtk.h"
33 #include "scrollbar-gtk.h"
34
35 #include "frame.h"
36 #include "window.h"
37
38 static gboolean scrollbar_cb (GtkAdjustment *adj, gpointer user_data);
39
40 /* Used to prevent changing the size of the slider while drag
41    scrolling, under Motif.  This is necessary because the Motif
42    scrollbar is incredibly stupid about updating the slider and causes
43    lots of flicker if it is done too often.  */
44 static int inhibit_slider_size_change;
45
46 static int vertical_drag_in_progress;
47
48 \f
49 /* A device method. */
50 static int
51 gtk_inhibit_scrollbar_slider_size_change (void)
52 {
53   return inhibit_slider_size_change;
54 }
55
56 /* A device method. */
57 static void
58 gtk_free_scrollbar_instance (struct scrollbar_instance *instance)
59 {
60   if (SCROLLBAR_GTK_WIDGET (instance))
61     {
62       gtk_widget_hide_all (SCROLLBAR_GTK_WIDGET (instance));
63       gtk_widget_destroy (SCROLLBAR_GTK_WIDGET (instance));
64     }
65
66   if (instance->scrollbar_data)
67     xfree (instance->scrollbar_data);
68 }
69
70 /* A device method. */
71 static void
72 gtk_release_scrollbar_instance (struct scrollbar_instance *instance)
73 {
74     /* It won't hurt to hide it all the time, will it? */
75     gtk_widget_hide_all (SCROLLBAR_GTK_WIDGET (instance));
76 }
77
78 static gboolean
79 scrollbar_drag_hack_cb (GtkWidget *w, GdkEventButton *ev, gpointer v)
80 {
81   vertical_drag_in_progress = (int) v;
82   inhibit_slider_size_change = (int) v;
83   return (FALSE);
84 }
85
86 /* A device method. */
87 static void
88 gtk_create_scrollbar_instance (struct frame *f, int vertical,
89                                struct scrollbar_instance *instance)
90 {
91   GtkAdjustment *adj = GTK_ADJUSTMENT (gtk_adjustment_new (0,0,0,0,0,0));
92   GtkScrollbar *sb = NULL;
93
94   /* initialize the X specific data section. */
95   instance->scrollbar_data = xnew_and_zero (struct gtk_scrollbar_data);
96
97   SCROLLBAR_GTK_ID (instance) = new_gui_id ();
98   SCROLLBAR_GTK_VDRAG_ORIG_VALUE (instance) = -1;
99   SCROLLBAR_GTK_LAST_VALUE (instance) = adj->value;
100
101   gtk_object_set_data (GTK_OBJECT (adj), GTK_DATA_GUI_IDENTIFIER,
102                        (void *) SCROLLBAR_GTK_ID (instance));
103   gtk_object_set_data (GTK_OBJECT (adj), GTK_DATA_FRAME_IDENTIFIER, f);
104   gtk_object_set_data (GTK_OBJECT (adj), "xemacs::sb_instance", instance);
105
106   sb = GTK_SCROLLBAR (vertical ? gtk_vscrollbar_new (adj) : gtk_hscrollbar_new (adj));
107   /* With gtk version > 1.2.8 the gtk code does not call
108      gtk_widget_request_size() on the newly created scrollbars
109      anymore (catering to theme engines).
110      #### Maybe it is better to postpone this call to just before
111      gtk_widget_show() is called on the scrollbar? */
112 #if GTK_MAJOR_VERSION == 1 && GTK_MINOR_VERSION == 2 && GTK_BINARY_AGE > 8
113   gtk_widget_size_request(GTK_WIDGET(sb), &(GTK_WIDGET(sb)->requisition));
114 #endif  
115   SCROLLBAR_GTK_WIDGET (instance) = GTK_WIDGET (sb);
116
117   gtk_signal_connect (GTK_OBJECT (adj),"value-changed",
118                       GTK_SIGNAL_FUNC (scrollbar_cb), (gpointer) vertical);
119
120   gtk_signal_connect (GTK_OBJECT (sb), "button-press-event",
121                       GTK_SIGNAL_FUNC (scrollbar_drag_hack_cb), (gpointer) 1);
122   gtk_signal_connect (GTK_OBJECT (sb), "button-release-event",
123                       GTK_SIGNAL_FUNC (scrollbar_drag_hack_cb), (gpointer) 0);
124
125   gtk_fixed_put (GTK_FIXED (FRAME_GTK_TEXT_WIDGET (f)), SCROLLBAR_GTK_WIDGET (instance), 0, 0);
126   gtk_widget_hide (SCROLLBAR_GTK_WIDGET (instance));
127 }
128
129 #define UPDATE_DATA_FIELD(field)                                \
130   if (new_##field >= 0 &&                                       \
131       SCROLLBAR_GTK_POS_DATA (inst).field != new_##field) {     \
132     SCROLLBAR_GTK_POS_DATA (inst).field = new_##field;          \
133     inst->scrollbar_instance_changed = 1;                       \
134   }
135
136 /* A device method. */
137 /* #### The -1 check is such a hack. */
138 static void
139 gtk_update_scrollbar_instance_values (struct window *w,
140                                       struct scrollbar_instance *inst,
141                                       int new_line_increment,
142                                       int new_page_increment,
143                                       int new_minimum, int new_maximum,
144                                       int new_slider_size,
145                                       int new_slider_position,
146                                       int new_scrollbar_width,
147                                       int new_scrollbar_height,
148                                       int new_scrollbar_x, int new_scrollbar_y)
149 {
150   UPDATE_DATA_FIELD (line_increment);
151   UPDATE_DATA_FIELD (page_increment);
152   UPDATE_DATA_FIELD (minimum);
153   UPDATE_DATA_FIELD (maximum);
154   UPDATE_DATA_FIELD (slider_size);
155   UPDATE_DATA_FIELD (slider_position);
156   UPDATE_DATA_FIELD (scrollbar_width);
157   UPDATE_DATA_FIELD (scrollbar_height);
158   UPDATE_DATA_FIELD (scrollbar_x);
159   UPDATE_DATA_FIELD (scrollbar_y);
160
161   if (w && !vertical_drag_in_progress)
162     {
163       int new_vov = SCROLLBAR_GTK_POS_DATA (inst).slider_position;
164       int new_vows = marker_position (w->start[CURRENT_DISP]);
165
166       if (SCROLLBAR_GTK_VDRAG_ORIG_VALUE (inst) != new_vov)
167         {
168           SCROLLBAR_GTK_VDRAG_ORIG_VALUE (inst) = new_vov;
169           inst->scrollbar_instance_changed = 1;
170         }
171       if (SCROLLBAR_GTK_VDRAG_ORIG_WINDOW_START (inst) != new_vows)
172         {
173           SCROLLBAR_GTK_VDRAG_ORIG_WINDOW_START (inst) = new_vows;
174           inst->scrollbar_instance_changed = 1;
175         }
176     }
177 }
178
179 /* Used by gtk_update_scrollbar_instance_status. */
180 static void
181 update_one_widget_scrollbar_pointer (struct window *w, GtkWidget *wid)
182 {
183   if (!wid->window)
184     gtk_widget_realize (wid);
185
186   if (POINTER_IMAGE_INSTANCEP (w->scrollbar_pointer))
187     {
188       gdk_window_set_cursor (GET_GTK_WIDGET_WINDOW (wid),
189                              XIMAGE_INSTANCE_GTK_CURSOR (w->scrollbar_pointer));
190       gdk_flush ();
191     }
192 }
193
194 /* A device method. */
195 static void
196 gtk_update_scrollbar_instance_status (struct window *w, int active, int size,
197                                       struct scrollbar_instance *instance)
198 {
199   struct frame *f = XFRAME (w->frame);
200   GtkWidget *wid = SCROLLBAR_GTK_WIDGET (instance);
201   gboolean managed = GTK_WIDGET_MAPPED (wid);
202
203   if (active && size)
204     {
205       if (instance->scrollbar_instance_changed)
206         {
207           /* Need to set the height, width, and position of the widget */
208           GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (wid));
209           scrollbar_values *pos_data = & SCROLLBAR_GTK_POS_DATA (instance);
210           int modified_p = 0;
211
212           /* We do not want to update the size all the time if we can
213              help it.  This cuts down on annoying flicker.
214           */
215           if ((wid->allocation.width != pos_data->scrollbar_width) ||
216               (wid->allocation.height != pos_data->scrollbar_height))
217             {
218               gtk_widget_set_usize (wid,
219                                     pos_data->scrollbar_width,
220                                     pos_data->scrollbar_height);
221
222               /*
223                 UGLY! UGLY! UGLY!  Changes to wid->allocation are queued and
224                 not performed until the GTK event loop.  However, when the
225                 fontlock progress bar is run, the vertical scrollbar's height
226                 is change and then changed back before events are again
227                 processed.  This means that the change back is not seen and
228                 the scrollbar is left too short.  Fix this by making the
229                 change manually so the test above sees the change.  This does
230                 not seem to cause problems in other cases.
231                */
232
233               wid->allocation.width = pos_data->scrollbar_width;
234               wid->allocation.height = pos_data->scrollbar_height;
235
236               modified_p = 1;
237             }
238
239           /* Ditto for the x/y position. */
240           if ((wid->allocation.x != pos_data->scrollbar_x) ||
241               (wid->allocation.y != pos_data->scrollbar_y))
242             {
243               gtk_fixed_move (GTK_FIXED (FRAME_GTK_TEXT_WIDGET (f)),
244                               wid,
245                               pos_data->scrollbar_x,
246                               pos_data->scrollbar_y);
247
248               /*
249                 UGLY! UGLY! UGLY!  Changes to wid->allocation are queued and
250                 not performed until the GTK event loop.  However, when the
251                 fontlock progress bar is run, the horizontal scrollbar's
252                 position is change and then changed back before events are
253                 again processed.  This means that the change back is not seen
254                 and the scrollbar is left in the wrong position.  Fix this by
255                 making the change manually so the test above sees the change.
256                 This does not seem to cause problems in other cases.
257                */
258
259               wid->allocation.x = pos_data->scrollbar_x;
260               wid->allocation.y = pos_data->scrollbar_y;
261
262               modified_p = 1;
263             }
264
265           adj->lower = pos_data->minimum;
266           adj->upper = pos_data->maximum;
267           adj->page_increment = pos_data->slider_size + 1;
268           adj->step_increment = w->max_line_len - 1;
269           adj->page_size = pos_data->slider_size + 1;
270           adj->value = pos_data->slider_position;
271
272           /* But, if we didn't resize or move the scrollbar, the
273              widget will not get redrawn correctly when the user
274              scrolls around in the XEmacs frame manually.  So we
275              update the slider manually here.
276           */
277           if (!modified_p)
278             gtk_range_slider_update (GTK_RANGE (wid));
279
280           instance->scrollbar_instance_changed = 0;
281         }
282
283       if (!managed)
284         {
285           gtk_widget_show (wid);
286           update_one_widget_scrollbar_pointer (w, wid);
287         }
288     }
289   else if (managed)
290     {
291       gtk_widget_hide (wid);
292     }
293 }
294
295 enum gtk_scrollbar_loop
296 {
297   GTK_FIND_SCROLLBAR_WINDOW_MIRROR,
298   GTK_SET_SCROLLBAR_POINTER,
299   GTK_WINDOW_IS_SCROLLBAR,
300   GTK_UPDATE_FRAME_SCROLLBARS
301 };
302
303 static struct window_mirror *
304 gtk_scrollbar_loop (enum gtk_scrollbar_loop type, Lisp_Object window,
305                     struct window_mirror *mir,
306                     GUI_ID id, GdkWindow *x_win)
307 {
308   struct window_mirror *retval = NULL;
309
310   while (mir)
311     {
312       struct scrollbar_instance *vinstance = mir->scrollbar_vertical_instance;
313       struct scrollbar_instance *hinstance = mir->scrollbar_horizontal_instance;
314       struct window *w = XWINDOW (window);
315
316       if (mir->vchild)
317         retval = gtk_scrollbar_loop (type, w->vchild, mir->vchild, id, x_win);
318       else if (mir->hchild)
319         retval = gtk_scrollbar_loop (type, w->hchild, mir->hchild, id, x_win);
320       if (retval)
321         return retval;
322
323       if (hinstance || vinstance)
324         {
325           switch (type)
326             {
327             case GTK_FIND_SCROLLBAR_WINDOW_MIRROR:
328               if ((vinstance && SCROLLBAR_GTK_ID (vinstance) == id) ||
329                   (hinstance && SCROLLBAR_GTK_ID (hinstance) == id))
330                 return mir;
331               break;
332             case GTK_UPDATE_FRAME_SCROLLBARS:
333               if (!mir->vchild && !mir->hchild)
334                 update_window_scrollbars (w, mir, 1, 0);
335               break;
336             case GTK_SET_SCROLLBAR_POINTER:
337               if (!mir->vchild && !mir->hchild)
338                 {
339                   GtkWidget *widget;
340
341                   widget = SCROLLBAR_GTK_WIDGET (hinstance);
342                   if (widget && GTK_WIDGET_MAPPED (widget))
343                     update_one_widget_scrollbar_pointer (w, widget);
344
345                   widget = SCROLLBAR_GTK_WIDGET (vinstance);
346                   if (widget && GTK_WIDGET_MAPPED (widget))
347                     update_one_widget_scrollbar_pointer (w, widget);
348                 }
349               break;
350             case GTK_WINDOW_IS_SCROLLBAR:
351               if (!mir->vchild && !mir->hchild)
352                 {
353                   GtkWidget *widget;
354
355                   widget = SCROLLBAR_GTK_WIDGET (hinstance);
356                   if (widget && GTK_WIDGET_MAPPED (widget) &&
357                       GET_GTK_WIDGET_WINDOW (widget) == x_win)
358                     return (struct window_mirror *) 1;
359
360                   widget = SCROLLBAR_GTK_WIDGET (vinstance);
361                   if (widget && GTK_WIDGET_MAPPED (widget) &&
362                       GET_GTK_WIDGET_WINDOW (widget) == x_win)
363                     return (struct window_mirror *) 1;
364                 }
365               break;
366             default:
367               ABORT ();
368             }
369         }
370
371       mir = mir->next;
372       window = w->next;
373     }
374
375   return NULL;
376 }
377
378 /* Used by callbacks. */
379 static struct window_mirror *
380 find_scrollbar_window_mirror (struct frame *f, GUI_ID id)
381 {
382   if (f->mirror_dirty)
383     update_frame_window_mirror (f);
384   return gtk_scrollbar_loop (GTK_FIND_SCROLLBAR_WINDOW_MIRROR, f->root_window,
385                              f->root_mirror, id, (GdkWindow *) NULL);
386 }
387
388 static gboolean
389 scrollbar_cb (GtkAdjustment *adj, gpointer user_data)
390 {
391   /* This function can GC */
392   int vertical = (int) user_data;
393   struct frame *f = gtk_object_get_data (GTK_OBJECT (adj), GTK_DATA_FRAME_IDENTIFIER);
394   struct scrollbar_instance *instance = gtk_object_get_data (GTK_OBJECT (adj), "xemacs::sb_instance");
395   GUI_ID id = (GUI_ID) gtk_object_get_data (GTK_OBJECT (adj), GTK_DATA_GUI_IDENTIFIER);
396   Lisp_Object win, frame;
397   struct window_mirror *mirror;
398   Lisp_Object event_type = Qnil;
399   Lisp_Object event_data = Qnil;
400
401   if (!f)
402     return(FALSE);
403
404   mirror = find_scrollbar_window_mirror (f, id);
405   if (!mirror)
406     return(FALSE);
407   
408   win = real_window (mirror, 1);
409
410   if (NILP (win))
411     return(FALSE);
412   instance = vertical ? mirror->scrollbar_vertical_instance : mirror->scrollbar_horizontal_instance;
413   frame = WINDOW_FRAME (XWINDOW (win));
414
415   inhibit_slider_size_change = 0;
416   switch (GTK_RANGE (SCROLLBAR_GTK_WIDGET (instance))->scroll_type)
417     {
418     case GTK_SCROLL_PAGE_BACKWARD:
419       event_type = vertical ? Qscrollbar_page_up : Qscrollbar_page_left;
420       event_data = Fcons (win, Qnil);
421       break;
422     case GTK_SCROLL_PAGE_FORWARD:
423       event_type = vertical ? Qscrollbar_page_down : Qscrollbar_page_right;
424       event_data = Fcons (win, Qnil);
425       break;
426     case GTK_SCROLL_STEP_FORWARD:
427       event_type = vertical ? Qscrollbar_line_down : Qscrollbar_char_right;
428       event_data = win;
429       break;
430     case GTK_SCROLL_STEP_BACKWARD:
431       event_type = vertical ? Qscrollbar_line_up : Qscrollbar_char_left;
432       event_data = win;
433       break;
434     case GTK_SCROLL_NONE:
435     case GTK_SCROLL_JUMP:
436       inhibit_slider_size_change = 1;
437       event_type = vertical ? Qscrollbar_vertical_drag : Qscrollbar_horizontal_drag;
438       event_data = Fcons (win, make_int ((int)adj->value));
439       break;
440     default:
441       ABORT();
442     }
443
444   signal_special_gtk_user_event (frame, event_type, event_data);
445
446   return (TRUE);
447 }
448
449 static void
450 gtk_scrollbar_pointer_changed_in_window (struct window *w)
451 {
452   Lisp_Object window;
453
454   XSETWINDOW (window, w);
455   gtk_scrollbar_loop (GTK_SET_SCROLLBAR_POINTER, window, find_window_mirror (w),
456                       0, (GdkWindow *) NULL);
457 }
458
459 /* #### BILL!!! This comment is not true for Gtk - should it be? */
460 /* Make sure that all scrollbars on frame are up-to-date.  Called
461    directly from gtk_set_frame_properties in frame-gtk.c*/
462 void
463 gtk_update_frame_scrollbars (struct frame *f)
464 {
465   /* Consider this code to be "in_display" so that we ABORT() if Fsignal()
466      gets called. */
467   in_display++;
468   gtk_scrollbar_loop (GTK_UPDATE_FRAME_SCROLLBARS, f->root_window, f->root_mirror,
469                       0, (GdkWindow *) NULL);
470   in_display--;
471   if (in_display < 0) ABORT ();
472 }
473
474 #ifdef MEMORY_USAGE_STATS
475 static int
476 gtk_compute_scrollbar_instance_usage (struct device *d,
477                                       struct scrollbar_instance *inst,
478                                       struct overhead_stats *ovstats)
479 {
480   int total = 0;
481
482   while (inst)
483     {
484       struct gtk_scrollbar_data *data =
485         (struct gtk_scrollbar_data *) inst->scrollbar_data;
486
487       total += malloced_storage_size (data, sizeof (*data), ovstats);
488       inst = inst->next;
489     }
490
491   return total;
492 }
493
494 #endif /* MEMORY_USAGE_STATS */
495
496
497 /************************************************************************/
498 /*                            initialization                            */
499 /************************************************************************/
500
501 void
502 console_type_create_scrollbar_gtk (void)
503 {
504   CONSOLE_HAS_METHOD (gtk, inhibit_scrollbar_slider_size_change);
505   CONSOLE_HAS_METHOD (gtk, free_scrollbar_instance);
506   CONSOLE_HAS_METHOD (gtk, release_scrollbar_instance);
507   CONSOLE_HAS_METHOD (gtk, create_scrollbar_instance);
508   CONSOLE_HAS_METHOD (gtk, update_scrollbar_instance_values);
509   CONSOLE_HAS_METHOD (gtk, update_scrollbar_instance_status);
510   CONSOLE_HAS_METHOD (gtk, scrollbar_pointer_changed_in_window);
511 #ifdef MEMORY_USAGE_STATS
512   CONSOLE_HAS_METHOD (gtk, compute_scrollbar_instance_usage);
513 #endif /* MEMORY_USAGE_STATS */
514 }
515
516 void
517 vars_of_scrollbar_gtk (void)
518 {
519   Fprovide (intern ("gtk-scrollbars"));
520 }