martinwguy martinwguy - 18 days ago 10
C Question

How do I make a GTK3 image-in-a-window shrinkable?

I have a simple GTK3 app that displays an image from a file in a window.
When you resize the window, the image is scaled in the expose callback to fit the window.

However, once the window has grown, you can't shrink it again; the resize handles only let you make the window ever bigger.

With GTK2 it was trivial to allow grow and shrink with gtk_window_set_policy(w,1,1,1).

How can the same effect be achieved in GTK3?

Here's the ever-growing code example:

#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

gboolean resize_image(GtkWidget *widget, GdkEvent *event, void *data)
{
GdkPixbuf *pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(widget));
if (pixbuf == NULL)
{
g_printerr("Failed to get pixbuf\n");
return 1;
}

pixbuf = gdk_pixbuf_scale_simple(pixbuf,
widget->allocation.width, widget->allocation.height,
GDK_INTERP_BILINEAR);

gtk_image_set_from_pixbuf(GTK_IMAGE(widget), pixbuf);

return FALSE;
}

int main(int argc, char **argv)
{
GtkWidget *window = NULL;
GtkWidget *image = NULL;

if (argc < 2 || argc > 3)
{
g_printerr("Usage: %s <image>\n", argv[0]);
return 1;
}

gtk_init(&argc, &argv);

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
image = gtk_image_new_from_file(argv[1]);
if (image == NULL)
{
g_printerr("Could not open \"%s\"\n", argv[1]);
return 1;
}

g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(image, "expose-event", G_CALLBACK(resize_image), NULL);

gtk_container_add(GTK_CONTAINER(window), image);
gtk_widget_show_all(GTK_WIDGET(window));

gtk_main();

return 0;
}

Answer

Eric Cecashon on the gtk-list mailing list suggests using a cairo drawing area inside a 1x1 grid container, which works fairly well:

/*
    gcc -Wall da_resize.c -o da_resize `pkg-config gtk+-3.0 --cflags --libs`
    Tested on Ubuntu16.04, GTK3.18.
*/

#include<gtk/gtk.h>

gboolean draw_picture(GtkWidget *da, cairo_t *cr, gpointer data)
{
  gint width=gtk_widget_get_allocated_width(da);
  gint height=gtk_widget_get_allocated_height(da);

  GdkPixbuf *temp=gdk_pixbuf_scale_simple((GdkPixbuf*)data, width, height, GDK_INTERP_BILINEAR);
  gdk_cairo_set_source_pixbuf(cr, temp, 0, 0);
  cairo_paint(cr);

  g_object_unref(temp);
  return FALSE;
}
int main(int argc, char *argv[])
{
  gtk_init(&argc, &argv);

  GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Resize Picture");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);

  g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

  //Needs a valid picture.
  GdkPixbuf *pixbuf=gdk_pixbuf_new_from_file(argc>1 ? argv[1] : "image.jpg", NULL);

  GtkWidget *da1=gtk_drawing_area_new();
  gtk_widget_set_hexpand(da1, TRUE);
  gtk_widget_set_vexpand(da1, TRUE);
  g_signal_connect(da1, "draw", G_CALLBACK(draw_picture), pixbuf);

  GtkWidget *grid=gtk_grid_new();
  gtk_grid_attach(GTK_GRID(grid), da1, 0, 0, 1, 1);

  gtk_container_add(GTK_CONTAINER(window), grid);
  gtk_widget_show_all(window);

  gtk_main();

  g_object_unref(pixbuf);

  return 0;
}