Mercurial > emacs
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; |