comparison src/gtkutil.c @ 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 66a7f2850b56
children 61194aef8668
comparison
equal deleted inserted replaced
50176:45278f351181 50177:297925dd73b1
33 #include "termhooks.h" 33 #include "termhooks.h"
34 #include <gdk/gdkkeysyms.h> 34 #include <gdk/gdkkeysyms.h>
35 35
36 #define FRAME_TOTAL_PIXEL_HEIGHT(f) \ 36 #define FRAME_TOTAL_PIXEL_HEIGHT(f) \
37 (PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f)) 37 (PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f))
38
39
40 /* Key to save a struct xg_last_sb_pos in the scroll bars. */
41 #define XG_LAST_SB_POS "emacs_last_sb_pos"
42
43
44 /* Use this in scroll bars to keep track of when redraw is really needed. */
45 struct xg_last_sb_pos
46 {
47 int portion, position, whole;
48 };
49 38
50 39
51 40
52 /*********************************************************************** 41 /***********************************************************************
53 Utility functions 42 Utility functions
296 285
297 xg_frame_set_char_size (f, columns, rows); 286 xg_frame_set_char_size (f, columns, rows);
298 gdk_window_process_all_updates (); 287 gdk_window_process_all_updates ();
299 } 288 }
300 289
301 /* When the Emacs frame is resized, we must call this function for each
302 child of our GtkFixed widget. The children are scroll bars and
303 we invalidate the last position data here so it will be properly
304 redrawn later when xg_update_scrollbar_pos is called.
305 W is the child widget.
306 CLIENT_DATA is not used. */
307 static handle_fixed_child (w, client_data)
308 GtkWidget *w;
309 gpointer client_data;
310 {
311 struct xg_last_sb_pos *last_pos
312 = g_object_get_data (G_OBJECT (w), XG_LAST_SB_POS);
313 if (last_pos)
314 {
315 last_pos->portion = last_pos->position = last_pos->whole = .1;
316 }
317 }
318
319 /* This gets called after the frame F has been cleared. Since that is 290 /* This gets called after the frame F has been cleared. Since that is
320 done with X calls, we need to redraw GTK widget (scroll bars). */ 291 done with X calls, we need to redraw GTK widget (scroll bars). */
321 void 292 void
322 xg_frame_cleared (f) 293 xg_frame_cleared (f)
323 FRAME_PTR f; 294 FRAME_PTR f;
324 { 295 {
325 GtkWidget *wfixed = f->output_data.x->edit_widget; 296 GtkWidget *wfixed = f->output_data.x->edit_widget;
326 297
327 if (wfixed) 298 if (wfixed)
328 { 299 {
329 gtk_container_foreach (GTK_CONTAINER (wfixed),
330 (GtkCallback) handle_fixed_child,
331 NULL);
332 gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE); 300 gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE);
333 gdk_window_process_all_updates (); 301 gdk_window_process_all_updates ();
334 } 302 }
335 } 303 }
336 304
2275 2243
2276 /* Setting scroll bar values invokes the callback. Use this variable 2244 /* Setting scroll bar values invokes the callback. Use this variable
2277 to indicate that callback should do nothing. */ 2245 to indicate that callback should do nothing. */
2278 int xg_ignore_gtk_scrollbar; 2246 int xg_ignore_gtk_scrollbar;
2279 2247
2280 /* After we send a scroll bar event, x_set_toolkit_scroll_bar_thumb will
2281 be called. For some reason that needs to be debugged, it gets called
2282 with bad values. Thus, we set this variable to ignore those calls. */
2283 int xg_ignore_next_thumb;
2284
2285 /* SET_SCROLL_BAR_X_WINDOW assumes the second argument fits in 2248 /* SET_SCROLL_BAR_X_WINDOW assumes the second argument fits in
2286 32 bits. But we want to store pointers, and they may be larger 2249 32 bits. But we want to store pointers, and they may be larger
2287 than 32 bits. Keep a mapping from integer index to widget pointers 2250 than 32 bits. Keep a mapping from integer index to widget pointers
2288 to get around the 32 bit limitation. */ 2251 to get around the 32 bit limitation. */
2289 static struct 2252 static struct
2389 gpointer p; 2352 gpointer p;
2390 int id = (int)data; 2353 int id = (int)data;
2391 2354
2392 p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_DATA); 2355 p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_DATA);
2393 if (p) xfree (p); 2356 if (p) xfree (p);
2394 p = g_object_get_data (G_OBJECT (widget), XG_LAST_SB_POS);
2395 if (p) xfree (p);
2396 xg_remove_widget_from_map (id); 2357 xg_remove_widget_from_map (id);
2397 } 2358 }
2398 2359
2399 /* Callback for button press/release events. Used to start timer so that 2360 /* Callback for button press/release events. Used to start timer so that
2400 the scroll bar repetition timer in GTK gets handeled. 2361 the scroll bar repetition timer in GTK gets handeled.
2362 Also, sets bar->dragging to Qnil when dragging (button release) is done.
2401 WIDGET is the scroll bar widget the event is for (not used). 2363 WIDGET is the scroll bar widget the event is for (not used).
2402 EVENT contains the event. 2364 EVENT contains the event.
2403 USER_DATA is 0 (not used). 2365 USER_DATA points to the struct scrollbar structure.
2404 2366
2405 Returns FALSE to tell GTK that it shall continue propagate the event 2367 Returns FALSE to tell GTK that it shall continue propagate the event
2406 to widgets. */ 2368 to widgets. */
2407 static gboolean 2369 static gboolean
2408 scroll_bar_button_cb (widget, event, user_data) 2370 scroll_bar_button_cb (widget, event, user_data)
2410 GdkEventButton *event; 2372 GdkEventButton *event;
2411 gpointer user_data; 2373 gpointer user_data;
2412 { 2374 {
2413 if (event->type == GDK_BUTTON_PRESS && ! xg_timer) 2375 if (event->type == GDK_BUTTON_PRESS && ! xg_timer)
2414 xg_start_timer (); 2376 xg_start_timer ();
2415 else if (event->type == GDK_BUTTON_RELEASE && xg_timer) 2377 else if (event->type == GDK_BUTTON_RELEASE)
2416 xg_stop_timer (); 2378 {
2417 2379 struct scroll_bar *bar = (struct scroll_bar *) user_data;
2380 if (xg_timer) xg_stop_timer ();
2381 bar->dragging = Qnil;
2382 }
2383
2418 return FALSE; 2384 return FALSE;
2419 } 2385 }
2420 2386
2421 /* Create a scroll bar widget for frame F. Store the scroll bar 2387 /* Create a scroll bar widget for frame F. Store the scroll bar
2422 in BAR. 2388 in BAR.
2458 /* Connect to button press and button release to detect if any scroll bar 2424 /* Connect to button press and button release to detect if any scroll bar
2459 has the pointer. */ 2425 has the pointer. */
2460 g_signal_connect (G_OBJECT (wscroll), 2426 g_signal_connect (G_OBJECT (wscroll),
2461 "button-press-event", 2427 "button-press-event",
2462 G_CALLBACK (scroll_bar_button_cb), 2428 G_CALLBACK (scroll_bar_button_cb),
2463 0); 2429 (gpointer)bar);
2464 g_signal_connect (G_OBJECT (wscroll), 2430 g_signal_connect (G_OBJECT (wscroll),
2465 "button-release-event", 2431 "button-release-event",
2466 G_CALLBACK (scroll_bar_button_cb), 2432 G_CALLBACK (scroll_bar_button_cb),
2467 0); 2433 (gpointer)bar);
2468 2434
2469 gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget), 2435 gtk_fixed_put (GTK_FIXED (f->output_data.x->edit_widget),
2470 wscroll, 0, 0); 2436 wscroll, -1, -1);
2471 2437
2472 /* Set the cursor to an arrow. */ 2438 /* Set the cursor to an arrow. */
2473 xg_set_cursor (wscroll, &xg_left_ptr_cursor); 2439 xg_set_cursor (wscroll, &xg_left_ptr_cursor);
2474 2440
2475 /* Allocate a place to hold the last scollbar size. GTK redraw for
2476 scroll bars is basically clear all, and then redraw. This flickers
2477 a lot since xg_update_scrollbar_pos gets called on every cursor move
2478 and a lot more places. So we have this to check if a redraw really
2479 is needed. */
2480 struct xg_last_sb_pos *last_pos
2481 = (struct xg_last_sb_pos *) xmalloc (sizeof (struct xg_last_sb_pos));
2482 last_pos->portion = last_pos->position = last_pos->whole = -1;
2483 g_object_set_data (G_OBJECT (wscroll), XG_LAST_SB_POS, last_pos);
2484
2485 SET_SCROLL_BAR_X_WINDOW (bar, scroll_id); 2441 SET_SCROLL_BAR_X_WINDOW (bar, scroll_id);
2486 } 2442 }
2487 2443
2488 /* Make the scroll bar represented by SCROLLBAR_ID visible. */ 2444 /* Make the scroll bar represented by SCROLLBAR_ID visible. */
2489 void 2445 void
2507 gtk_widget_destroy (w); 2463 gtk_widget_destroy (w);
2508 SET_FRAME_GARBAGED (f); 2464 SET_FRAME_GARBAGED (f);
2509 } 2465 }
2510 } 2466 }
2511 2467
2468 /* Find left/top for widget W in GtkFixed widget WFIXED. */
2469 static void
2470 xg_find_top_left_in_fixed (w, wfixed, left, top)
2471 GtkWidget *w, *wfixed;
2472 int *left, *top;
2473 {
2474 GList *iter;
2475
2476 for (iter = GTK_FIXED (wfixed)->children; iter; iter = g_list_next (iter))
2477 {
2478 GtkFixedChild *child = (GtkFixedChild *) iter->data;
2479
2480 if (child->widget == w)
2481 {
2482 *left = child->x;
2483 *top = child->y;
2484 return;
2485 }
2486 }
2487
2488 /* Shall never end up here. */
2489 abort ();
2490 }
2512 2491
2513 /* Update the position of the vertical scroll bar represented by SCROLLBAR_ID 2492 /* Update the position of the vertical scroll bar represented by SCROLLBAR_ID
2514 in frame F. 2493 in frame F.
2515 TOP/LEFT are the new pixel positions where the bar shall appear. 2494 TOP/LEFT are the new pixel positions where the bar shall appear.
2516 WIDTH, HEIGHT is the size in pixels the bar shall have. */ 2495 WIDTH, HEIGHT is the size in pixels the bar shall have. */
2523 int width; 2502 int width;
2524 int height; 2503 int height;
2525 { 2504 {
2526 2505
2527 GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id); 2506 GtkWidget *wscroll = xg_get_widget_from_map (scrollbar_id);
2528 2507
2529 if (wscroll) 2508 if (wscroll)
2530 { 2509 {
2510 GtkWidget *wfixed = f->output_data.x->edit_widget;
2531 int gheight = max (height, 1); 2511 int gheight = max (height, 1);
2532 struct xg_last_sb_pos *last_pos 2512 int canon_width = FRAME_SCROLL_BAR_COLS (f) * CANON_X_UNIT (f);
2533 = g_object_get_data (G_OBJECT (wscroll), XG_LAST_SB_POS); 2513 int winextra = canon_width > width ? (canon_width - width) / 2 : 0;
2534 GtkWidget *wfixed = f->output_data.x->edit_widget; 2514 int bottom = top + gheight;
2535 2515
2536 last_pos->portion = last_pos->position = last_pos->whole = -1; 2516 gint slider_width;
2537 xg_ignore_next_thumb = 0; 2517 int oldtop, oldleft, oldbottom;
2538 2518 GtkRequisition req;
2519
2520 /* Get old values. */
2521 xg_find_top_left_in_fixed (wscroll, wfixed, &oldleft, &oldtop);
2522 gtk_widget_size_request (wscroll, &req);
2523 oldbottom = oldtop + req.height;
2524
2525 /* Scroll bars in GTK has a fixed width, so if we say width 16, it
2526 will only be its fixed width (14 is default) anyway, the rest is
2527 blank. We are drawing the mode line across scroll bars when
2528 the frame is split:
2529 |bar| |fringe|
2530 ----------------
2531 mode line
2532 ----------------
2533 |bar| |fringe|
2534
2535 When we "unsplit" the frame:
2536
2537 |bar| |fringe|
2538 -| |-| |
2539 m¦ |i| |
2540 -| |-| |
2541 | | | |
2542
2543
2544 the remains of the mode line can be seen in these blank spaces.
2545 So we must clear them explicitly.
2546 GTK scroll bars should do that, but they don't.
2547 Also, the scroll bar canonical width may be wider than the width
2548 passed in here. */
2549
2550 if (oldtop != -1 && oldleft != -1)
2551 {
2552 int gtkextra;
2553 int xl, xr, wblank;
2554 int bottomdiff, topdiff;
2555
2556 gtk_widget_style_get (wscroll, "slider_width", &slider_width, NULL);
2557 gtkextra = width > slider_width ? (width - slider_width) / 2 : 0;
2558
2559 xl = left - winextra;
2560 wblank = gtkextra + winextra;
2561 xr = left + gtkextra + slider_width;
2562 bottomdiff = abs (oldbottom - bottom);
2563 topdiff = abs (oldtop - top);
2564
2565 if (oldtop > top)
2566 {
2567 gdk_window_clear_area (wfixed->window, xl, top, wblank, topdiff);
2568 gdk_window_clear_area (wfixed->window, xr, top, wblank, topdiff);
2569 }
2570 else if (oldtop < top)
2571 {
2572 gdk_window_clear_area (wfixed->window, xl, oldtop, wblank,
2573 topdiff);
2574 gdk_window_clear_area (wfixed->window, xr, oldtop, wblank,
2575 topdiff);
2576 }
2577
2578 if (oldbottom > bottom)
2579 {
2580 gdk_window_clear_area (wfixed->window, xl, bottom, wblank,
2581 bottomdiff);
2582 gdk_window_clear_area (wfixed->window, xr, bottom, wblank,
2583 bottomdiff);
2584 }
2585 else if (oldbottom < bottom)
2586 {
2587 gdk_window_clear_area (wfixed->window, xl, oldbottom, wblank,
2588 bottomdiff);
2589 gdk_window_clear_area (wfixed->window, xr, oldbottom, wblank,
2590 bottomdiff);
2591 }
2592 }
2593
2594 /* Move and resize to new values. */
2539 gtk_fixed_move (GTK_FIXED (wfixed), wscroll, left, top); 2595 gtk_fixed_move (GTK_FIXED (wfixed), wscroll, left, top);
2540 gtk_widget_set_size_request (wscroll, width, gheight); 2596 gtk_widget_set_size_request (wscroll, width, gheight);
2541 2597
2542 gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE); 2598 gtk_container_set_reallocate_redraws (GTK_CONTAINER (wfixed), TRUE);
2543 2599
2544 /* Must force out update so wscroll gets the resize. 2600 /* Make GTK draw the new sizes. We are not using a pure GTK event
2545 Otherwise, the gdk_window_clear clears the old window size. */ 2601 loop so we need to do this. */
2546 gdk_window_process_all_updates ();
2547
2548 /* The scroll bar doesn't explicitly redraw the whole window
2549 when a resize occurs. Since the scroll bar seems to be fixed
2550 in width it doesn't fill the space reserved, so we must clear
2551 the whole window. */
2552 gdk_window_clear (wscroll->window);
2553
2554 /* Since we are not using a pure gtk event loop, we must force out
2555 pending update events with this call. */
2556 gdk_window_process_all_updates (); 2602 gdk_window_process_all_updates ();
2557 2603
2558 SET_FRAME_GARBAGED (f); 2604 SET_FRAME_GARBAGED (f);
2559 cancel_mouse_face (f); 2605 cancel_mouse_face (f);
2560 } 2606 }
2568 int portion, position, whole; 2614 int portion, position, whole;
2569 { 2615 {
2570 GtkWidget *wscroll = xg_get_widget_from_map (SCROLL_BAR_X_WINDOW (bar)); 2616 GtkWidget *wscroll = xg_get_widget_from_map (SCROLL_BAR_X_WINDOW (bar));
2571 2617
2572 FRAME_PTR f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window))); 2618 FRAME_PTR f = XFRAME (WINDOW_FRAME (XWINDOW (bar->window)));
2573 struct xg_last_sb_pos *last_pos
2574 = (wscroll ? g_object_get_data (G_OBJECT (wscroll), XG_LAST_SB_POS) : 0);
2575 2619
2576 BLOCK_INPUT; 2620 BLOCK_INPUT;
2577 if (wscroll 2621
2578 && ! xg_ignore_next_thumb 2622 if (wscroll && NILP (bar->dragging))
2579 && last_pos
2580 && (last_pos->portion != portion
2581 || last_pos->position != position
2582 || last_pos->whole != whole))
2583 { 2623 {
2584 GtkAdjustment *adj; 2624 GtkAdjustment *adj;
2585 gdouble shown; 2625 gdouble shown;
2586 gdouble top; 2626 gdouble top;
2587 int size, value; 2627 int size, value;
2588 2628
2589 adj = gtk_range_get_adjustment (GTK_RANGE (wscroll)); 2629 adj = gtk_range_get_adjustment (GTK_RANGE (wscroll));
2630
2631 /* We do the same as for MOTIF in xterm.c, assume 30 chars per line
2632 rather than the real portion value. This makes the thumb less likely
2633 to resize and that looks better. */
2634 portion = XFASTINT (XWINDOW (bar->window)->height) * 30;
2635 /* When the thumb is at the bottom, position == whole.
2636 So we need to increase `whole' to make space for the thumb. */
2637 whole += portion;
2590 2638
2591 if (whole <= 0) 2639 if (whole <= 0)
2592 top = 0, shown = 1; 2640 top = 0, shown = 1;
2593 else 2641 else
2594 { 2642 {
2602 2650
2603 value = top * whole; 2651 value = top * whole;
2604 value = min (value, whole - size); 2652 value = min (value, whole - size);
2605 value = max (value, XG_SB_MIN); 2653 value = max (value, XG_SB_MIN);
2606 2654
2607 adj->upper = max (whole, size);
2608 adj->page_size = (int)size; 2655 adj->page_size = (int)size;
2609
2610 /* Assume a page increment is about 95% of the page size */
2611 adj->page_increment = (int) (0.95*adj->page_size);
2612
2613 /* Assume all lines are equal. */
2614 adj->step_increment = portion / max (1, FRAME_HEIGHT (f));
2615
2616 if (last_pos)
2617 {
2618 last_pos->portion = portion;
2619 last_pos->position = position;
2620 last_pos->whole = whole;
2621 }
2622 2656
2623 /* gtk_range_set_value invokes the callback. Set 2657 /* gtk_range_set_value invokes the callback. Set
2624 ignore_gtk_scrollbar to make the callback do nothing */ 2658 ignore_gtk_scrollbar to make the callback do nothing */
2625 xg_ignore_gtk_scrollbar = 1; 2659 xg_ignore_gtk_scrollbar = 1;
2660
2661 gtk_range_set_range (GTK_RANGE (wscroll), adj->lower, max (whole, size));
2662
2663 /* Assume all lines are of equal size. */
2664 /* Assume a page increment is about 95% of the page size */
2665 gtk_range_set_increments (GTK_RANGE (wscroll),
2666 portion / max (1, FRAME_HEIGHT (f)),
2667 (int) (0.95*adj->page_size));
2668
2626 gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value); 2669 gtk_range_set_value (GTK_RANGE (wscroll), (gdouble)value);
2627 xg_ignore_gtk_scrollbar = 0; 2670 xg_ignore_gtk_scrollbar = 0;
2628 2671
2629 /* Make sure the scroll bar is redrawn with new thumb */ 2672 /* Make GTK draw the new thumb. We are not using a pure GTK event
2630 gtk_widget_queue_draw (wscroll); 2673 loop so we need to do this. */
2631 } 2674 gdk_window_process_all_updates ();
2632 2675 }
2633 gdk_window_process_all_updates (); 2676
2634 xg_ignore_next_thumb = 0;
2635 UNBLOCK_INPUT; 2677 UNBLOCK_INPUT;
2636 } 2678 }
2637 2679
2638 2680
2639 /*********************************************************************** 2681 /***********************************************************************
3077 ***********************************************************************/ 3119 ***********************************************************************/
3078 void 3120 void
3079 xg_initialize () 3121 xg_initialize ()
3080 { 3122 {
3081 xg_ignore_gtk_scrollbar = 0; 3123 xg_ignore_gtk_scrollbar = 0;
3082 xg_ignore_next_thumb = 0;
3083 xg_left_ptr_cursor = 0; 3124 xg_left_ptr_cursor = 0;
3084 xg_did_tearoff = 0; 3125 xg_did_tearoff = 0;
3085 3126
3086 xg_menu_cb_list.prev = xg_menu_cb_list.next = 3127 xg_menu_cb_list.prev = xg_menu_cb_list.next =
3087 xg_menu_item_cb_list.prev = xg_menu_item_cb_list.next = 0; 3128 xg_menu_item_cb_list.prev = xg_menu_item_cb_list.next = 0;