GNOME Bugzilla – Bug 1165
Handle late setting of adjustments in scrollable widgets
Last modified: 2015-06-23 11:30:54 UTC
Package: gtk+ Version: 1.2.1 Bug description: When using a scrolled window widget (with a widget added with a viewport), that has been realised, if new adjustments are set (with set_hadjustment() and set_vadjustment()), then two display problems occur: 1. The X server is not informed of the new position of view on the inserted widget, and thus sends exposure events for the wrong part of the inserted widget. 2. The scroll bars are not redrawn correctly until they have been unrealised and realised again. The first problem is resolved as soon as the scroll bars are used; things suddenly jump into the correct position. As far as I can tell this is because only the callback from the scroll bars being adjusted calls gdk_window_move() (which in turn informs the X server with XWindowMove()). gdk_window_move() is not called by either set_hadjustment() or set_vadjustment() (this is probably the best fix). As a work around the scroll bars can be nudged (add one to the adjustment current position, then take it off again); this is my current work around and gives the correct display eventually (although some unwanted redrawing is done -- this is noticeable in my instance because the widget drawing code involves a lot of calculations and hence takes a lot of time). The second problem is resolved when, eg, the window is resized. As a work around I've been turning the scroll bars off and then on again (setting policy = NEVER, then setting policy = AUTOMATIC). Unfortunately both the nudging and the scroll bar unrealising/realising need to be done with the widget in the viewport realised which results in extra redrawing. (The nudging, etc, does not take effect if the inserted widget is not realised.) Operating environment: Debian slink, with manually installed Glib 1.2.1, GTK+ 1.2.1, GTK-- 1.0. Linux 2.2.1 kernel Xfree86 X server (3.3.2.3a) (SVGA, for Matrox Millennium (I) card) Using GTK-- C++ wrapper for GTK+, from C++ code, compiled with EGCS 1.1.1. The problem appears in both 1.2.1 (which I've done most of my testing on), and as far as I can tell in a very brief test in 1.2.2. The 1.2.1->1.2.2 patch doesn't indicate any changes in this area. The GTK-- wrappers are simply calling the GTK+ functions directly (with little other handling going on), and this and reading the GTK+ code are the reason for thinking it is GTK+ not taking the correct steps. Reproducing the bug: Unfortunately I found these problems (and hacked around them) in the middle of a large chunk of C++ code which I can't make available. I also haven't yet had the time to write up a small sample program that exhibits the problem. As far as I can tell the problem can be repeated by: 1. Creating a scrolled window, with a suitable widget inside it that reports on the expose events it is getting (I'm using a drawing area), and set the scroll bar policies to AUTOMATIC. 2. Realise everything. 3. Giving the scrolled window new adjustments with a different range (ie lower and upper) and a different value. (In my instance I'm "zooming" so the range is normally 2-3 times as big, and the position is scaled appropriately. But the problem seems to occur irrespective of whether the adjustment range gets bigger or smaller.) This should repeat the wrong-expose-events problem. The scroll bar issue also seems to occur in the same manner, but is most obvious when attempts at scrolling around result in scrollbars that will not "scroll" even though based on the size of the view and the size of the widget inside it, it should scroll. Resizing the window should "fix" this problem. (I realise it's a real pain not having example code that produces the problem, and if you want to throw it back at me and say "provide an example" then I'll do that. It just might take a while. Same with patches.) Ewen ------- Additional Comments From otaylor@redhat.com 1999-05-05 12:06:32 ---- Subject: GTK+: Scrolled window: set new adjustments doesn't inform X server From: Owen Taylor <otaylor@redhat.com> To: 1165@bugs.gnome.org Cc: Ewen McNeill <ewen@naos.co.nz> Message-Id: <ybek8uncxh3.fsf@fresnel.labs.redhat.com> Date: 05 May 1999 12:06:32 -0400 An example program would in fact be most helpful here. (Also, it would be preferred if it were straight GTK+ as opposed to GTK--) Thanks, Owen ------- Additional Comments From ewen@naos.co.nz 1999-05-09 23:22:55 ---- Subject: Re: GTK+: Scrolled window: set new adjustments doesn't inform X server From: Ewen McNeill <ewen@naos.co.nz> To: 1165@bugs.gnome.org Message-Id: <199905100322.PAA07468@pagoda.wgtn.naos.co.nz> Date: Mon, 10 May 1999 15:22:55 +1200 [This is a duplicate copy of a message that was bounced by the GTK bug tracking service: Hi. This is the qmail-send program at tirania.nuclecu.unam.mx. I'm afraid I wasn't able to deliver your message to the following addresses. This is a permanent error; I've given up. Sorry it didn't work out. <1165@bugs.gnome.org>: / Unknown ticket service address owen taylor <otaylor@bugs.gnome.org Recognised addresses are: / General: Read # in Subject: Ticket # is NNNN: ...... Owen: I've BCC'd you on this message, in order to avoid confusing the bug tracking service with your address, in case that was what had it stumped. ] In message <ybek8uncxh3.fsf@fresnel.labs.redhat.com>, Owen Taylor writes: >An example program would in fact be most helpful here. >(Also, it would be preferred if it were straight GTK+ as opposed to GTK--) One example, in GTK+ 1.2.1. The code is really hacked together but it does show the problems that I reported in bug 1165, with one slight variation: I've found it seems to be sufficient to hide/show the _drawing_area_ (ie, contained widget) in order to get the scroll bars drawn correctly again (previously I reported it was necessary to turn the scroll bars off/on in the scrolled window). The example code is loosely based on the scrolledwin demo example with GTK+ 1.2.1, but has had bits hacked into it. It should compile with the standard sort of Makefile for those examples, viz: -=- cut here -=- CC = gcc scrolledwin: scrolledwin.c $(CC) `gtk-config --cflags` -g scrolledwin.c -o scrolledwin `gtk-config --libs` clean: rm -f *.o scrolledwin -=- cut here -=- When compiled and run, the program displays a scrolled window containing a drawing area, and a small control bar at the bottom. The drawing area simply draws the position of the window in every so often across the exposed area (makes it easy to tell what is being drawn). The control bar contains: 1. An "adjust" button, used to set the scrolled window scroll bar adjustments to random values (all less than 32767; an X limitation which I haven't seen documented in the GTK docs) 2. Two checkboxes which can turn on and off the hacks I've been using: "hide/show" -- will hide the drawing area and show it again in order to force a repaint (I don't think this should be necessary when the scrolled window works properly) "nudge" -- nudges the scroll bar after setting adjustments (done by setting the value to one more than prev value), which forces flush of position update through to the X server. To see the problems I reported: 1. Compile and run the program. Click on adjust without selecting any checkboxes. Try the scroll bars. Note how they don't scroll any more! 2. Toggle the "hide/show" checkbox. Click on adjust. Note that the drawn area doesn't correspond with the area that the scroll bars have been adjusted to. Click on one of the scroll bars. Note how it suddenly springs into the correct position. (For maximum effect click on the move-by-1 arrow at the end of the scroll bar; a big jump for moving by 1 :-) ) 3. Toggle the "nudge" checkbox. Click on "adjust". Notice how it is now going to the correct place and redisplaying. Situation 3 is how I believe it should behave without needing the hide/show hack or the nudge scrollbar hack. The very act of setting the adjustments should flush a new position thorugh to the X server. Although doing this when they have to be set independently is possibly unfortunate (if the widget is visible it could be double redrawn), but it should be done at least once without any hacks required in the application. If you've got any questions, etc, please feel free to get in touch. Note that the code is a hack to demo the problem, and definitely leaks memory, and has very poor style, because it was hacked together in a hurry. But these shouldn't affect the thing being demonstrated. Source code follows. Tested with Debian Slink (2.1), with Linux 2.2.1 kernel, with Glib 1.2.1 and GTK+ 1.2.1. C compiler is ewen@pagoda:/src/local/niwa/libs/gtk-bugs $ gcc -v Reading specs from /usr/lib/gcc-lib/i486-linux/2.7.2.3/specs gcc version 2.7.2.3 Bugs also noted with GTK-- (C++ wrapper for GTK+) using GTK-- 1.0, Glib 1.2.1 and GTK+ 1.2.1, and with a very brief test of the program using GTK-- appeared to be there with Glib 1.2.2 and GTK+ 1.2.2 (The patch to GTK+ 1.2.2 didn't seem to affect files in the relevant areas.) Ewen -=- cut here -=- /* Demonstration of bug in GTK+ 1.2.1 with scrolling windows, and * changing adjustments: (Reported as GTK bug 1165) * 1. The X server is not informed of the new position of view on the * inserted widget, and thus sends exposure events for the wrong * part of the inserted widget until the scroll bars are adjusted. * * 2. The scroll bars are not redrawn correctly until they have been * unrealised and realised again (or at least the drawing area * hidden and shown). * * The "nudge scrollbar" toggle can be used to turn on and off the * workaround for the first problem. * * The "show/hide" toggle can be used to turn on and off the workaround * for the second problem. * * Hacked together by Ewen McNeill <ewen@naos.co.nz>, 1999/5/10, * based on: scrolledwin example. * * NOTE: Quite a few of the bits in this code are just hacked together, with * just enough to show what goes wrong, and it'll, eg, leak memory. */ /* example-start scrolledwin scrolledwin.c */ #include <gtk/gtk.h> #include <stdlib.h> static int show_expose = 2; /* 0 = don't; 1 = after config; 2 = always */ static GtkWidget *workaround_1_checkbox; /* Workaround one: hide/show */ static int workaround_1 = 0; static GtkWidget *workaround_2_checkbox; /* Workaround two: nudge bar */ static int workaround_2 = 0; static GtkWidget *scrolled_window; /* Scrolled window area */ static GtkWidget *drawing_area; /* Drawing area */ void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit(); } /* Set new adjustments for the window. We malloc the adjustments in * order to ensure they won't get overwritten, but leak them afterwards * for simplicity of this demo hack. (This shouldn't affect the bug.) * * The adjustments are set to random values in order to ensure they * change each time around. */ void new_adjustments(GtkWidget *widget, gpointer data) { int mx = (int)(rand()*1.0*32767.0/(RAND_MAX*1.0)); /* 0..32767 */ int my = (int)(rand()*1.0*32767.0/(RAND_MAX*1.0)); /* 0..32767 */ int px = (int)((rand()*1.0)*(mx*1.0)/(RAND_MAX*1.0)); /* 0..mx */ int py = (int)((rand()*1.0)*(my*1.0)/(RAND_MAX*1.0)); /* 0..my */ GtkAdjustment *x_adjust = (GtkAdjustment *)gtk_adjustment_new(px, 0, mx, 1, 10, 300); GtkAdjustment *y_adjust = (GtkAdjustment *)gtk_adjustment_new(py, 0, my, 1, 10, 300); GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(scrolled_window); GtkDrawingArea *da = GTK_DRAWING_AREA(drawing_area); printf("Adjusting X to %d/%d\n", px, mx); printf("Adjusting Y to %d/%d\n", py, my); gtk_drawing_area_size(da, mx, my); gtk_scrolled_window_set_hadjustment(sw, x_adjust); gtk_scrolled_window_set_vadjustment(sw, y_adjust); /* Force a redraw (hide and show the drawing area) */ if (workaround_1) { printf("Hiding and showing drawing area\n"); gtk_widget_hide(drawing_area); gtk_widget_show(drawing_area); } if (workaround_2) { printf("Nudging scroll bar\n"); gtk_adjustment_set_value(x_adjust, px+1); } } static gint configure_event (GtkWidget *widget, GdkEventConfigure *event) { printf("Configuring for %dx%d\n", widget->allocation.width, widget->allocation.height); if (! show_expose) show_expose = 1; } static gint expose_event (GtkWidget *widget, GdkEventExpose *event) { char posstr[128]; int x, y; int minx, miny; int maxx, maxy; GdkFont *fixed_font; fixed_font = gdk_font_load ("-misc-fixed-medium-r-*-*-*-140-*-*-*-*-*-*"); if (show_expose) printf("Expose event for %d,%d, of size %dx%d\n", event->area.x, event->area.y, event->area.width, event->area.height); if (show_expose < 2) show_expose = 0; /* Put some positioning kind of text into the window to show where * we are in the window */ minx = (event->area.x % 120 ? ((event->area.x/120)) * 120 : event->area.x); miny = (event->area.y % 50 ? ((event->area.y/50)) * 50 : event->area.y); maxx = event->area.x + event->area.width + 120; maxy = event->area.y + event->area.height + 50; for (y = miny; y < maxy; y+=50) { for (x = minx; x < maxx; x+=120) { sprintf(posstr, "%d,%d", x, y); gdk_draw_text(widget->window, fixed_font, widget->style->black_gc, x, y, posstr, strlen(posstr)); /* printf("Putting string [%s] at %d,%d\n", posstr, x, y); */ } } } void toggle_workaround_1(GtkWidget *widget, gpointer data) { workaround_1 = !workaround_1; printf("Show/Hide is now %s\n", workaround_1 ? "on" : "off"); } void toggle_workaround_2(GtkWidget *widget, gpointer data) { workaround_2 = !workaround_2; printf("Nudge Scrollbar is now %s\n", workaround_2 ? "on" : "off"); } int main (int argc, char *argv[]) { static GtkWidget *window; GtkWidget *table; GtkWidget *button; GtkWidget *hbox; char buffer[32]; int i, j; gtk_init (&argc, &argv); /* Create the drawing area */ drawing_area = gtk_drawing_area_new (); gtk_drawing_area_size (GTK_DRAWING_AREA (drawing_area), 600, 600); gtk_widget_show (drawing_area); /* Signals used to follow what is to be drawn */ gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event", (GtkSignalFunc) expose_event, NULL); gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event", (GtkSignalFunc) configure_event, NULL); gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); /* Create a new dialog window for the scrolled window to be * packed into. A dialog is just like a normal window except it has a * vbox and a horizontal separator packed into it. It's just a shortcut * for creating dialogs */ window = gtk_dialog_new (); gtk_signal_connect (GTK_OBJECT (window), "destroy", (GtkSignalFunc) destroy, NULL); gtk_window_set_title (GTK_WINDOW (window), "GtkScrolledWindow example"); gtk_container_set_border_width (GTK_CONTAINER (window), 0); gtk_widget_set_usize(window, 300, 300); /* create a new scrolled window. */ scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 10); /* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS. * GTK_POLICY_AUTOMATIC will automatically decide whether you need * scrollbars, whereas GTK_POLICY_ALWAYS will always leave the scrollbars * there. The first one is the horizontal scrollbar, the second, * the vertical. */ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); /* The dialog window is created with a vbox packed into it. */ gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, TRUE, TRUE, 0); gtk_widget_show (scrolled_window); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_window ), drawing_area); gtk_widget_show (drawing_area); /* Add the control panel thingy to the bottom. * We have an "adjust" button to activate the change, and some * toggle flags to turn on and off the hacks that work around * some of the problem. */ button = gtk_button_new_with_label ("adjust"); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) new_adjustments, NULL); hbox = gtk_hbox_new(FALSE, 0); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox, TRUE, TRUE, 0); workaround_1_checkbox = gtk_check_button_new_with_label("Hide/Show"); workaround_2_checkbox = gtk_check_button_new_with_label("Nudge"); workaround_1 = 0; workaround_2 = 0; gtk_box_pack_start(GTK_BOX(hbox), workaround_1_checkbox, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox), workaround_2_checkbox, TRUE, TRUE, 0); gtk_signal_connect(GTK_OBJECT(workaround_1_checkbox), "toggled", (GtkSignalFunc)toggle_workaround_1, NULL); gtk_signal_connect(GTK_OBJECT(workaround_2_checkbox), "toggled", (GtkSignalFunc)toggle_workaround_2, NULL); gtk_widget_show (workaround_1_checkbox); gtk_widget_show (workaround_2_checkbox); gtk_widget_show (button); gtk_widget_show (window); gtk_main(); return(0); } /* example-end */ -=- cut here -=- ------- Bug moved to this database by debbugs-export@bugzilla.gnome.org 2001-01-27 14:46 ------- This bug was previously known as bug 1165 at http://bugs.gnome.org/ http://bugs.gnome.org/show_bug.cgi?id=1165 Originally filed under the gtk+ product and general component. The original reporter (ewen@naos.co.nz) of this bug does not have an account here. Reassigning to the exporter, debbugs-export@bugzilla.gnome.org. Reassigning to the default owner of the component, gtk-bugs@gtk.org.
The problem here is, simply, put, gtk_viewport_set_[hv]adjustment() doesn't work after the widget is set up. This will be rather tricky to fix.
I'm moving this from the 1.2.9 milestone to the 2.0 milestone, because it is quite hard to fix, and it's not clear that the fix would be compatible with what people expect from 1.2.8. All scrolled widgets need to be checked for this, not just GtkViewport.
I've made a bunch of changes now that make it behave as I expect, though I not quite as is expected here. The thing to realize is that for a GtkViewport, the only free variable that can be controlled by the application is the value - the lower/upper/max/page_size/step_size are all under the control of the widget and are in units of pixels. Fri Jun 6 16:25:44 2003 Owen Taylor <otaylor@redhat.com> * gtk/gtkviewport.c: Many fixes, along with extensive cleanups and refactoring of code to reduce duplication; fixes include: - gtk_viewport_realize(): Position the window correct from adjustment values. (#110737, Michael Natterer) - Remove some division-by-zero checks in places where there is no longer division. (#110737) - gtk_viewport_class_init: Make the hadjustment/vadjustment properties G_PARAM_CONSTRUCT, so that there will always be adjustments, even if gtk_viewport_new isn't used (#101135, Thomas Leonard). - Switch over to encapsulated lazy-creation for hadjustment/ vadjustment; even with the CONSTRUCT property, we need this after destroy. - When updating the adjustment, immediate set their values to match the the current range of the viewport, and update the viewport position to match the value of the new adjustments. (Part of #1165)
"make it behave" == "make GtkViewport behave" - other scrolled widgets still need to be fixed.
See http://mail.gnome.org/archives/gtk-devel-list/2003-June/msg00027.html For discussion of the principles behind the changes.
I was the original bug reporter, back in 1999 (I've created a bugzilla account just to be able to say this!). In the interveening _five_ years I've seen dozens of reassignments of this bug to yet another later version of GTK+ (and/or Gnome). I've long since ceased to have anything to do with the project in which I encountered this bug (and that project continues to work using the work around I came up with -- which is described briefly above -- with some extra hacks to minimise the amount of redrawing done). Perhaps it would be more honest to mark this bug "won't fix" at this point. Or at least take me off the list of addresses notified of every deferrment of this bug to a future release, as I really don't wish to receive a notification of every single version that you have decided not to fix the bug in for yet more years. Ewen
Sorry, there's no way to take you off the Cc: list since you are the original reporter. Maybe you can come up with a procmail rule or mail filter to discard these messages. We've fixed this in *some* widgets now. It's still a legitimate TODO for other widgets so WONTFIX'ing would be unfortunate.
Here is a list of scrollable widgets which still need to be fixed: GtkTextView GtkTreeView GtkIconView GtkLayout less important, since deprecated, are GtkCList GtkText
Owens email linked above pretty clearly outlines what behaviour has to be implemented.
If this works, except for GtkCList and GtkText, shouldn't it have been closed?
I'd say so. But does that work as expected now?
Matthias, can you confirm is this bug report is still relevant today?
My comment from 10 years ago does not say "this works, except for clist and text". But regardless, keeping this bug open for another 10 years is not going to make the world a better place.