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