Problem with GtkScrollBar, gtk_window_resize and gtk_adjustment_set_value()

Some days ago I noticed that my Nim cairo drawing demo mentioned here

does not work properly when in Gnome Shell I move the GTK window to the top of screen to switch to fullscreen mode. But when I enlarge the window by grepping right left corner of the window with the mouse all is fine. I have never noticed that before. I stripped down the code to the example below. It opens a window, and when I click into the window with LMB the window is resized. Then scrollbars stop working, I cant move them. But clicking in the window a second time, enlargeing it again to same size, then scrollbars starts to work again. I hope this is the same problem as in my original code – as least I do not really understand that behaviour.

It seems that the critical part is calling setUpper() in the dareaConfigureCallback(). May that be an invalid operation? Well, maybe the Scrollbars are in an invalid state at this time? I have no real idea. This is for

x11-libs/gtk±3.24.8:3::gentoo

Of course I can try to provide a C code version of this code, but I hope that ebassi or the other dev can point me to my error without.

One additional note. To my question about cairo drawingarea examples with zooming/scrolling I got a private reply pointing to GitHub - tschoonj/gtkmm-plplot: a scientific plotting library for Gtkmm leveraging the power of PLplot by its author – private as the discourse thread was already timed out.

import gintro/[gtk, gdk, glib, gobject, gio, cairo]

type
  PosAdj = Adjustment

proc newPosAdj: PosAdj =
  initAdjustment(result, 0, 0, 200, 10, 10, 100) # value, lower, upper, step_increment, page_increment, page_size

type
  PDA = ref object of Grid
    darea: DrawingArea
    hadjustment: PosAdj
    vadjustment: PosAdj
    hscrollbar: Scrollbar
    vscrollbar: Scrollbar
 
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
  echo ">>>", this.darea.allocatedWidth
  echo ">>>", this.darea.allocatedHeight
  this.hadjustment.setUpper(this.darea.allocatedWidth.float)
  this.vadjustment.setUpper(this.darea.allocatedHeight.float)
  return true

proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
  let w = gtk.Window(getTopLevel(this))
  w.resize(1000, 1200)

proc newPDA: PDA =
  initGrid(result)
  result.darea = newDrawingArea()
  result.darea.setHExpand
  result.darea.setVExpand
  result.darea.connect("configure-event", dareaConfigureCallback, result)
  result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
      EventFlag.scroll, button1Motion, button2Motion, pointerMotionHint})
  result.darea.connect("button_press_event", buttonPressEvent, result)
  result.hadjustment = newPosAdj()
  result.vadjustment = newPosAdj()
  result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
  result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
  result.hscrollbar.setHExpand
  result.vscrollbar.setVExpand
  result.attach(result.darea, 0, 0, 1, 1)
  result.attach(result.vscrollbar, 1, 0, 1, 1)
  result.attach(result.hscrollbar, 0, 1, 1, 1)

proc appActivate(app: Application) =
  let window = newApplicationWindow(app)
  window.defaultSize = (800, 600)
  let pda = newPDA()
  window.add(pda)
  showAll(window)

proc newDisplay() =
  let app = newApplication("org.gtk.example")
  connect(app, "activate", appActivate)
  discard run(app)

when isMainModule:
  newDisplay()

Well ebassi and other devs,

I get the feeling that my assumption is not that wrong: I did a test with realize signal instead of configure-event, and the sliders of the scrollbars continue working. So I think that with configure-event I am calling set_new_value() for adjustment at a point in time when scrollbars are not ready to accept that. Maybe internal scrollbar behaviour has changed, as I never noticed this before – and not 10 years ago with my old Ruby code with similar shape. Thinking about it in more detail, using configure-event is not the best solution, as I have to call set-value() only when size of window changes. But configure-event is fired for other reasons too, I think when parts of window are uncovered and needs redraw. So I will try to use more fine grained signals, like size-alloc and realize. I do not know much about it details, maybe I have to invest in which sequence they are fired.

Generally I will do some testing how big signal and event fllooding currently is. I can remember that I was suffering from flooding 10 years ago with my Ruby apps, I think I complained on mailing list about it that time. Flooding is of course a problem for apps doing detailed cairo drawing, as cairo is slow already, and drawing the same content multiple times just as many uneded draw signals come in is a waste.

PS: I have again considered using a GtkViewPort and a GtkScrolledView to hold the GtkDrawingArea for Apps with zoom, scroll, panning and all that. Well so I would get the scrollbars for free. But handling all other actions seems to be more difficult then, and for zooming: For a 4000x3000 pixel window, zoomed in by factor 10, I would have cairo surfaces 40000x30000 I guess? May be doable, as we have much Ram today.

The test with the realize signal was wrong unfortunately – I have now discovered that the realize signal is only emitted one time, when the applications starts. Not again when window is resized.

But I think I do know the problem now: It is generally a bad idea to connect to configure-event of drawing-area and modify adjustments of Scrollbar in that callback function. Seems to be must cleaner to connect to configure-event of Scrollbars directly and modify parameters in Scrollbar callback function. First test seems to indicate that it works then, will continue testing. Unfortunately the 13 draw events when a widget is unselected persists…

Finally I get the impression that there is no easy and clean solution to my problem.

If you do not remember: Core problem is: I have a GtkScrollBar bound to a GtkAdjustemt in a GtkGrid, and I want to call gtk_adjustment_set_upper() and gtk_adjustment_set_value() when the main window is resized. It works when main window is resized by dragging lower right corner, so changing size in small steps. But it works not when window is small and it it moved to top of screen to make it fullscreen, and it also does not work when manually gtk_window_resize() is called to resize main window significantly. For this case Scrollbars behave wired, this is not moving at all or only very slow. Until window is resized again by grabbing lower right corner.

I am still not sure if this is a gtk bug. I think it should work, as when GtkAdjustment is changed, Adjustment should emit an signal/event to scrollbar, and scrollbar should adjust itself.

There may be solutions – for example the stupid timer event. Or maybe I can define a custom event, which I emit when main window is resized, and I connect scrollbars to this event? But this seems to be too complicated, and maybe it will not work at all, as timing may still be critical.

So maybe I should fall back to plain solution: Never modify ScrollBars when main window is resized, but recalculate data from ScrollBar for actual size of DrawingArea, so that cairo drawing works with correctly scaled data. I think I considered this variant already 10 years ago for first Ruby draft of a drawing app.

Hi!
I have not tested your code by now, but can you try using the size-allocate signal instead of the configure-event?

Yes, I have used size-allocate, makes no real difference.

I have googled yesterday nearly the whole day for all the related stuff and did a lot of testing, but no real success.

I can provide C examples of the related code (have to create them, as I am doing testing with Nim.) But I will only do that if someone thinks that he has a real idea how it should work, because I am not that good a writing plain C, so it take some time for me.

I am really surprised that it is so hard. I think that some years ago it has worked the way I did it, maybe it has changed due to new gtk versions. Or maybe due to the fact that I am now using GtkGrid, while I used GtkTable in the past.

My impression is that configure-event and size-allocate are the only two sources that can be used when resize occur, and for both gtk_adjustment_set_upper() does not work.

The whole resizing process is not that transparent unfortunately. For example, when I resize main window which contains a grid, in which sequence are the signals emitted for parent and childs?

OK, have created a plain C example.

The test is done only with the horizontal scroll bar at the bottom of the window.

Works fine when we resize the window by dragging its right lower corner. Scrollbar can be moved and values are print out. But when we move the window to top of screen to maximize it, then scrollbar stops working, it does not move. When we move window back from top of screen to make it small again, scrollbar still does not move, but we get output printed. To make it work again, we can resize the window. Or using the right vertical scrollbar, that fixed it also.

My initial fear was, that allocated_Width would be invalid in callback, but we print it out, and it seems to be valid. So it seems to be a bug to me? Maybe only for my current gtk±3.24.8 with wayland? May it make sense to ship an issue to gitlab?

// https://developer.gnome.org/gnome-devel-demos/stable/hello-world.html.en
// gcc filename.c `pkg-config --cflags --libs gtk+-3.0` -o filename

#include <gtk/gtk.h>
#include <stdio.h>

static gboolean
configure_event_cb (GtkWidget *widget,
               GdkEvent  *event,
               gpointer   user_data)
{
  printf("configure_event_cb called\n");
  int h = gtk_widget_get_allocated_width(widget);
  printf("allocated_width %d\n", h);
  gtk_adjustment_set_upper(GTK_ADJUSTMENT (user_data), (gdouble) h);
  return FALSE;
}

static gboolean
change_value_cb (GtkWidget    *widget,
               gdouble val,
               gpointer      user_data)
{
  printf("change_value_cb called, %f\n", val);
  return FALSE;
}

static void
activate (GtkApplication* app,
          gpointer        user_data)
{
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *darea;
  GtkWidget *hscrollbar;
  GtkWidget *vscrollbar;
  GtkAdjustment *hadjustment;
  GtkAdjustment *vadjustment;
  window = gtk_application_window_new (app);
  grid = gtk_grid_new();
  darea = gtk_drawing_area_new();
  gtk_widget_set_hexpand(grid, TRUE);
  gtk_widget_set_vexpand(grid, TRUE);
  hadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);
  vadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);
  hscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, hadjustment);
  vscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
  gtk_widget_set_hexpand(hscrollbar, TRUE);
  gtk_widget_set_vexpand(vscrollbar, TRUE);
  gtk_grid_attach(GTK_GRID (grid), darea, 0, 0, 1, 1);
  gtk_grid_attach(GTK_GRID (grid), vscrollbar, 1, 0, 1, 1);
  gtk_grid_attach(GTK_GRID (grid), hscrollbar, 0, 1, 1, 1);
  g_signal_connect (darea, "configure-event",
                    G_CALLBACK (configure_event_cb), hadjustment);
  g_signal_connect (hscrollbar, "change-value",
                    G_CALLBACK (change_value_cb), NULL);
  gtk_container_add (GTK_CONTAINER (window), grid);
  gtk_widget_show_all(window);
}

int
main (int    argc,
      char **argv)
{
  GtkApplication *app;
  int status;
  app = gtk_application_new (NULL, G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}

Hi Stephan,

Have you tried gtk_scrolled_window_new()? Will this work? I had troubles with gtk_scrollbar_new() also. The scrolled window works on my computer.

I made it to Ubuntu 18.04 so tested the following with GTK 3.22.30.

Eric

// https://developer.gnome.org/gnome-devel-demos/stable/hello-world.html.en

// gcc scroll_test1.c pkg-config --cflags --libs gtk+-3.0 -o scroll_test1

#include <gtk/gtk.h>
#include <stdio.h>

static gboolean
configure_event_cb (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
printf(“configure_event_cb called\n”);
int h = gtk_widget_get_allocated_width(widget);
printf(“allocated_width %d\n”, h);
gtk_adjustment_set_upper(GTK_ADJUSTMENT (user_data), (gdouble) h);
return FALSE;
}

static gboolean
change_value_cb (GtkRange *range, GtkScrollType scroll, gdouble val, gpointer user_data)
{
printf(“change_value_cb called, %f\n”, val);
return FALSE;
}

static gboolean paint_da(GtkWidget *da, cairo_t *cr, gpointer user_data)
{
cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
cairo_paint(cr);
cairo_set_line_width(cr, 10.0);
cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 200.0, 200.0);
cairo_stroke(cr);
}

static void
activate (GtkApplication* app,
gpointer user_data)
{
GtkWidget *window;
GtkWidget *grid;
GtkWidget *darea;
GtkWidget *hscrollbar;
GtkWidget *vscrollbar;
GtkAdjustment *hadjustment;
GtkAdjustment *vadjustment;
window = gtk_application_window_new (app);
grid = gtk_grid_new();
darea = gtk_drawing_area_new();
gtk_widget_set_size_request(darea, 200, 200);

gtk_widget_set_hexpand(grid, TRUE);
gtk_widget_set_vexpand(grid, TRUE);

hadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);
vadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);

/*
hscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, hadjustment);
vscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
gtk_widget_set_hexpand(hscrollbar, TRUE);
gtk_widget_set_vexpand(vscrollbar, TRUE);
*/

GtkWidget *scrolled_window=gtk_scrolled_window_new(hadjustment, vadjustment);
gtk_widget_set_hexpand(scrolled_window, TRUE);
gtk_widget_set_vexpand(scrolled_window, TRUE);
hscrollbar=gtk_scrolled_window_get_hscrollbar(GTK_SCROLLED_WINDOW(scrolled_window));
gtk_container_add(GTK_CONTAINER(scrolled_window), darea);

gtk_grid_attach(GTK_GRID (grid), scrolled_window, 0, 0, 1, 1);

//gtk_grid_attach(GTK_GRID (grid), darea, 0, 0, 1, 1);
//gtk_grid_attach(GTK_GRID (grid), vscrollbar, 1, 0, 1, 1);
//gtk_grid_attach(GTK_GRID (grid), hscrollbar, 0, 1, 1, 1);

g_signal_connect (darea, “draw”, G_CALLBACK(paint_da), NULL);
g_signal_connect (darea, “configure-event”,
G_CALLBACK (configure_event_cb), hadjustment);
g_signal_connect (hscrollbar, “change-value”,
G_CALLBACK (change_value_cb), NULL);
gtk_container_add (GTK_CONTAINER (window), grid);
gtk_widget_show_all(window);
}

int
main (int argc,
char **argv)
{
GtkApplication *app;
int status;
app = gtk_application_new (NULL, G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, “activate”, G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}

Yes, I had considered using a GtkScrolledWindow. But note that my basic intent was to have a GtkDrawingArea widgets with scrollbars, and additional zoom/panning support. (That may be similar like used in Gimp or InkScape, but I think both are still at GTK2.)

GtkScrolledWindow is generally used for fixed size childs, often with an additional GtkViewPort. GtkDrawingArea has no fixed size, it can adapt to available space, but it needs info about current position when zoomed in to draw its content. See my old Ruby example and the new Nim one:

Homepage of Dr. Stefan Salewski
Homepage of Dr. Stefan Salewski
https://github.com/StefanSalewski/gintro/blob/master/examples/drawingarea.nim

I think one can make such app in a GtlScrolledWindow, when we increase size of GtkDrawingArea widget when zooming in. That may result in 100000 * 100000 Pixel DrawingArea widget which is clipped by GtkScrolledWindow. Possible, but sounds odd.

Maybe I should try testing my C example with GTK4. Have already started, but do not know how configure-event is replace exactly in GTK4. Mr Mac Lasen wrote something about that somewhere…

Please note that your pasted in code suffers from unicode.

Can you use cairo transforms to move the drawing around and zoom in on it? Cairo should be pretty fast doing that even if you scale the drawing to a larger size.

Eric

//gcc -Wall scroll_test2.c `pkg-config --cflags --libs gtk+-3.0` -o scroll_test2

#include <gtk/gtk.h>

static gdouble cursor_x=0.0;
static gdouble cursor_y=0.0;
static gdouble scale=1.0;
static gboolean zoom_drawing=TRUE;
static gboolean translate_drawing=TRUE;

static void activate(GtkApplication* app, gpointer user_data);
static void translate_check(GtkToggleButton *button, gpointer user_data);
static void zoom_check(GtkToggleButton *button, gpointer user_data);
static gboolean paint_da(GtkWidget *da, cairo_t *cr, gpointer user_data);
static gboolean zoom_in(GtkWidget *da, GdkEvent *event, gpointer user_data);

gint main(int argc, char **argv)
  {
    gint status;
    GtkApplication *app=gtk_application_new(NULL, G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK (activate), NULL);
    status=g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
  }
static void activate(GtkApplication* app, gpointer user_data)
  {
    GtkWidget *window=gtk_application_window_new(app);
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

    GtkWidget *check1=gtk_check_button_new_with_label("Translate/Translate & Zoom");
    gtk_widget_set_hexpand(check1, TRUE);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check1), TRUE);
    g_signal_connect(check1, "toggled", G_CALLBACK(translate_check), NULL);

    GtkWidget *check2=gtk_check_button_new_with_label("Zoom In/Out");
    gtk_widget_set_hexpand(check2, TRUE);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check2), TRUE);
    g_signal_connect(check2, "toggled", G_CALLBACK(zoom_check), NULL);

    GtkWidget *da=gtk_drawing_area_new();
    gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK);
    gtk_widget_set_size_request(da, 2000, 2000);
    g_signal_connect(da, "draw", G_CALLBACK(paint_da), NULL);
    g_signal_connect(da, "button-press-event", G_CALLBACK(zoom_in), NULL);
 
    GtkWidget *scrolled_window=gtk_scrolled_window_new(NULL, NULL);
    gtk_widget_set_hexpand(scrolled_window, TRUE);
    gtk_widget_set_vexpand(scrolled_window, TRUE);
    gtk_container_add(GTK_CONTAINER(scrolled_window), da);

    GtkWidget *grid=gtk_grid_new();
    gtk_grid_attach(GTK_GRID(grid), check1, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), check2, 1, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), scrolled_window, 0, 1, 2, 1);

    gtk_container_add(GTK_CONTAINER(window), grid);
    gtk_widget_show_all(window);
  }
static void translate_check(GtkToggleButton *button, gpointer user_data)
  {
    if(gtk_toggle_button_get_active(button)) translate_drawing=TRUE;
    else translate_drawing=FALSE;
  }
static void zoom_check(GtkToggleButton *button, gpointer user_data)
  {
    if(gtk_toggle_button_get_active(button)) zoom_drawing=TRUE;
    else zoom_drawing=FALSE;
  }
static gboolean paint_da(GtkWidget *da, cairo_t *cr, gpointer user_data)
  {
    GTimer *timer=g_timer_new();
    gdouble x=0.0;
    gdouble y=0.0;

    if(cursor_x>0.0||cursor_y>0.0)
      {
        x=cursor_x-200.0;
        y=cursor_y-200.0;
      }

    //Paint the background.
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    cairo_paint(cr);
    cairo_set_line_width(cr, 10.0);

    cairo_save(cr);
    //Draw with a fixed center.
    cairo_translate(cr, 200.0, 200.0);
    cairo_translate(cr, x, y);
    cairo_scale(cr, scale, scale);
    cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);
    cairo_rectangle(cr, -200.0, -200.0, 400.0, 400.0);
    cairo_stroke(cr);
    cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
    cairo_rectangle(cr, -150.0, -150.0, 300.0, 300.0);
    cairo_stroke(cr);
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
    cairo_rectangle(cr, -100.0, -100.0, 200.0, 200.0);
    cairo_stroke(cr);
    cairo_set_source_rgb(cr, 1.0, 0.0, 1.0);
    cairo_rectangle(cr, -50.0, -50.0, 100.0, 100.0);
    cairo_stroke(cr);
    cairo_set_source_rgb(cr, 0.0, 1.0, 1.0);
    cairo_arc(cr, 0.0, 0.0, 25.0, 0.0, 2.0*G_PI);
    cairo_stroke(cr);
    cairo_restore(cr);

    //Draw a guide circle.
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
    cairo_arc(cr, cursor_x, cursor_y, 5.0, 0.0, 2.0*G_PI);
    cairo_fill(cr);

    g_print("Time %f\n", g_timer_elapsed(timer, NULL));
    g_timer_destroy(timer);

    return FALSE;
  }
static gboolean zoom_in(GtkWidget *da, GdkEvent *event, gpointer user_data)
  {
    cursor_x=((GdkEventButton*)event)->x;
    cursor_y=((GdkEventButton*)event)->y;
    //Zoom in or out.
    if(!translate_drawing)
      {
        if(zoom_drawing&&scale<19.0) scale+=0.2;
        if(!zoom_drawing&&scale>0.3) scale-=0.2;
      }     
    gtk_widget_queue_draw(da);

    return FALSE;
  }

Thanks for your example. Indeed I asked for examples like your one some months ago, when I ported my drawing code from Ruby to Nim.

Your example has the big disadvantage, that when we zoom in, then scrolling over the whole area is not possible. I think what users generally expect is a behaviour like gimp – even when zoomed in by a large factor, scrolling over the whole picture content is possible. I think when using a GtkScrolledWindow that behaviour needs creating a new GtkDrawingArea for each zoom in step.

Generally I would be interested why my code example does not work correctly. Have looked already in the sources of gtk_scrollbar.c and gtk_adjustment.c, but have no idea. What is special in maximize() main window compared to increasing it by dragging right lower corner. And why seems gtk_adjustment_set_value() to work fine, while gtk_adjustment_set_upper() causes problems?

But gitlab gnome issue tracker has 20k open issues, and currently Mr M. Clasen seems to be the only one working on GTK, mostly GTK4 I think. But I have some hope that GTK4 will not have this issue.

You mentioned cairo performance. Recently I did a test with more complicated data structures, see

https://github.com/StefanSalewski/cdt

Yes, performance is not that bad. But maybe in the future we will get hardware drawing support. GTK4 seems to use Vulkan for drawing, maybe that will become available for widgets like GtkDrawingArea at some time?

For the issues in gitlab, I found

https://gitlab.gnome.org/GNOME/gtk/issues/650

I wonder if that may be related to our problem. I can remember that I got such warnings for many Gnome apps some years ago, which seems to have vanished now. Maybe someone tried to fix the warnings and broke the Scrollbar?

The whole GNOME GitLab instance has 20k issues; the GTK project has ~1100 issues open, which is still a lot, but definitely less. Nevertheless, yes: it’s not like we’re all here waiting for bugs to be working on; we have a lot of stuff to deal with, between maintaining GTK3, the rest of the GNOME core platform, and developing GTK4.

Additionally, Matthias is the GTK maintainer; there are only a couple of people working full time on GTK. Not everyone is here to answer your questions.

It’s up to you to figure it out, I’m afraid, since you’re using a combination of widgets that is not overly tested. Application developers typically use GtkScrolledWindow, which handles the panning of the viewport and keeps the adjustments, scroll bars, and visible area in sync, and takes care of the sizing of each child in response to changes in the allocation. Applications do not use a scroll bar to control a drawing area.

You’ll have to understand the size negotiation mechanism in GTK and GtkContainer.

Yes, for toy apps GtkScrolledWindow is fine.

But Do you think that some of the few remaining serious apps like Gimp or InkScape are using GtkScrolledWindow for their main drawing location? I have not looked into their code, as that code is really large, and I think both are still struggling with GTK2 → GTK3 conversion. I was pointed to Gtkmm-PLplot: Gtkmm-PLplot by its author lately, which seems to be a serious tool. But from my finding it uses no scrollbars at all unfortunately.

Maybe I should investigate gerbv, it was known for good cairo speed ten years ago, maybe there is some progress in GTK3 support going on. Its not obvious from their homepage:

https://sourceforge.net/p/gerbv/wiki/Home/

You have a very weird concept of “toy”, considering that everything uses GtkScrolledWindow.

Plus, apps like Gimp or Inkscape don’t use GtkDrawingArea in the first place. They have complex, custom widgets, and they handle the size negotiation themselves, instead of connecting to signals.

Well, I have to admit that I am not aware of many non-toy GTK/Gnome apps created or significantly updated in the last 10 years. Most what I know, like the geda tools gschem/pcb/gerbv/gtkwave or my daily used evolution mail are mostly in GTK2 style still. I think there will be some, for example some months ago I heard about a new paint program written in Python with GTK. But can not remember its name. Maybe we should have a thread about “great GTK3 tools” in this forum.

For my issue, after some more thinking, I guess I have made some progress: Regarding the comment of Jason Crain that handlers connected to signals are executed immediately without queuing, and the comment in

gtk_adjustment_value_changed has been deprecated since version 3.18 and should not be used in newly-written code.
GTK+ emits “value-changed” itself whenever the value changes

it begins to make some sense. When I call gtk_adjustment_set-upper() in my configure_event_callback, then the adjustment emits “changed” signal and scrollbar tries immediately to update it layout, while its size may be still undefined, leading to an illegal state. Before GTK 3.18 the GtkAdjustment does not emit the changed signal, so my older Ruby code was working without problems.

Something to give a try is to use the gtk_widget_set_size_request() when you zoom in or out. This works for me to resize the drawing area and adjust the scrollbars. Might have to change the translation a little to keep the drawing were you want it.

Eric

static gboolean zoom_in(GtkWidget *da, GdkEvent *event, gpointer user_data)
  {
    cursor_x=((GdkEventButton*)event)->x;
    cursor_y=((GdkEventButton*)event)->y;
    //Zoom in or out.
    if(!translate_drawing)
      {
        if(zoom_drawing&&scale<19.0) scale+=0.2; 
        if(!zoom_drawing&&scale>0.3) scale-=0.2;
      }  

    gint width=(gint)(400.0*scale);
    gint height=(gint)(400.0*scale);
    gtk_widget_set_size_request(da, width, height);   

    gtk_widget_queue_draw(da);

    return FALSE;
  }

With the hints from this forum and after reading the API docs again, I got the feeling that calling gtk_adjustment_set_upper() for a GtkAdjustment of a GtkScrollbar widget should be valid from inside the size-allocate callback of this single Scrollbar widget. One of the reasons why it should work is, that size-allocate signal has Flags: Run First, indicating that the default signal-handler runs before my own one. So all allocations should be done when I call gtk_adjustment_set_upper() for the GtkAdjustment.

And indeed, that seems to work without problems. I tried size-allocate signal early in my testings, but maybe I used that signal connected to my GtkDrawingArea, which is definitely wrong. What may be still critical is the value of the argument of gtk_adjustment_set_upper(). Generally it is the size of the DrawingArea, and that size may be invalid when gtk_adjustment_set_upper() is called. Fortunately the Scrollbar and the Drawingarea have same size, so I decided to use size of Scrollbar as argument. The code below seems to work fine, at least with my wayland box. I have made the changes already to my real Nim drawing app, works there fine too.

Two very little issue remain, maybe they are related: When I maximize the window by moving it to the top of screen, I get two size-allocate signal emissions, with size not equal like

size_allocate_cb called
allocated_width 3870
allocated_width 3870
size_allocate_cb called
allocated_width 3818
allocated_width 3818

And when I maximize the window this way, and move the mouse pointer over the scrollbar for the first time, it minimally changes its size.

But that are really minor issues – I am confident now that my code is not wrong :slight_smile:

// https://developer.gnome.org/gnome-devel-demos/stable/hello-world.html.en
// gcc filename.c `pkg-config --cflags --libs gtk+-3.0` -o filename

#include <gtk/gtk.h>
#include <stdio.h>

static void
size_allocate_cb (GtkWidget *widget, GdkRectangle *r, gpointer user_data)
{
  printf("size_allocate_cb called\n");
  int h = gtk_widget_get_allocated_width(widget);
  printf("allocated_width %d\n", h);
  printf("allocated_width %d\n", r->width);
  gtk_adjustment_set_upper(GTK_ADJUSTMENT(user_data), r->width);
}

static gboolean
change_value_cb (GtkWidget *widget, gdouble val, gpointer user_data)
{
  printf("change_value_cb called, %f\n", val);
  return FALSE;
}

static void
activate (GtkApplication* app, gpointer user_data)
{
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *darea;
  GtkWidget *hscrollbar;
  GtkWidget *vscrollbar;
  GtkAdjustment *hadjustment;
  GtkAdjustment *vadjustment;
  window = gtk_application_window_new (app);
  grid = gtk_grid_new();
  darea = gtk_drawing_area_new();
  gtk_widget_set_hexpand(grid, TRUE);
  gtk_widget_set_vexpand(grid, TRUE);
  hadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);
  vadjustment = gtk_adjustment_new(0.0, 0.0, 200.0, 1.0, 10.0, 100.0);
  hscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, hadjustment);
  vscrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
  gtk_widget_set_hexpand(hscrollbar, TRUE);
  gtk_widget_set_vexpand(vscrollbar, TRUE);
  gtk_grid_attach(GTK_GRID (grid), darea, 0, 0, 1, 1);
  gtk_grid_attach(GTK_GRID (grid), vscrollbar, 1, 0, 1, 1);
  gtk_grid_attach(GTK_GRID (grid), hscrollbar, 0, 1, 1, 1);
  g_signal_connect (hscrollbar, "size-allocate",
                    G_CALLBACK (size_allocate_cb), hadjustment);
  g_signal_connect (hscrollbar, "change-value",
                    G_CALLBACK (change_value_cb), NULL);
  gtk_container_add (GTK_CONTAINER (window), grid);
  gtk_widget_show_all(window);
}

int
main (int argc, char **argv)
{
  GtkApplication *app;
  int status;
  app = gtk_application_new (NULL, G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return status;
}

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.