Reputation: 23
I wish to have an image in my GTK app that continually resizes to fit its parent container.
I've accomplished this by getting the parent container's size inside a size-allocate event callback, and resizing my image according to those dimensions. This works fine when I'm making the window smaller, but when I want to make it bigger, it refuses to resize because it has to be at least as big as the contents (the image).
To overcome that aspect, I've placed the image in a ScrolledWindow so that I can freely resize my window smaller.
The issue lies in that when I switch the image shown to one with different dimensions, the ScrolledWindow doesn't seem to realize it, and I'm left with a ScrolledWindow with the wrong content size and unnecessary scroll bars. But alas, I can hover over the scroll bar and it realizes that it's too big for its content and removes the scroll bars. See the below demonstration.
Can I somehow have this "correction" behavior happen right away instead of when I hover over the scroll bars?
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GdkPixbuf
class Minimal(Gtk.Window):
imageShown = 0
img = Gtk.Image.new()
pixbufRed = GdkPixbuf.Pixbuf.new_from_file("kirby_red.png")
pixbufBlue = GdkPixbuf.Pixbuf.new_from_file("kirby_blue.png")
pixbuf = None
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(400,300)
button = Gtk.Button.new_with_label("Swap Image")
button.connect("clicked", self.on_button_click)
self.pixbuf = self.pixbufRed
self.img.set_from_pixbuf(self.pixbuf)
scrolled = Gtk.ScrolledWindow()
scrolled.connect("size-allocate", self.on_size_allocated);
scrolled.add(self.img)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,spacing=0)
box.pack_start(button, False, False, 0)
box.pack_end(scrolled, True, True, 0)
self.add(box)
#swap image shown using imageShown flag to keep track
def on_button_click(self, button):
if(self.imageShown == 0):
self.pixbuf = self.pixbufBlue
self.imageShown = 1
else:
self.pixbuf = self.pixbufRed
self.imageShown = 0
self.img.set_from_pixbuf(self.pixbuf)
def on_size_allocated(self, widget, allocation):
scaledPixbuf = Minimal.scale_image_from_allocation_keep_aspect(self.pixbuf, allocation)
self.img.set_from_pixbuf(scaledPixbuf)
@staticmethod
def scale_image_from_allocation_keep_aspect(pixbuf, allocation):
imgWidth = pixbuf.get_width()
imgHeight = pixbuf.get_height()
parentWidth = allocation.width
parentHeight = allocation.height
aspectWidth = parentWidth/imgWidth
aspectHeight= parentHeight/imgHeight
aspect=0
if(aspectWidth < aspectHeight):
aspect = aspectWidth
else:
aspect = aspectHeight
newWidth = imgWidth*aspect
newHeight = imgHeight*aspect
return pixbuf.scale_simple(newWidth, newHeight, GdkPixbuf.InterpType.BILINEAR)
win = Minimal()
win.show_all()
Gtk.main()
Upvotes: 1
Views: 656
Reputation: 2690
size-allocate
isn't really the right place to be changing the contents of your widget (like changing the image widget's pixbuf), and it usually doesn't work correctly if you try to use it like that. It's intended more for custom container widgets to layout their children once the size is already determined.
In GTK 3, I usually solve the problem of making images fill the available space by creating a very simple custom widget, like this:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, Gdk
class ScaleImage(Gtk.DrawingArea):
def __init__(self, pixbuf):
Gtk.DrawingArea.__init__(self)
self.pixbuf = pixbuf
def do_draw(self, cr):
alloc, baseline = self.get_allocated_size()
factor = min(alloc.width / self.pixbuf.get_width(), alloc.height / self.pixbuf.get_height())
cr.scale(factor, factor)
Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
cr.paint()
win = Gtk.Window()
img = ScaleImage(GdkPixbuf.Pixbuf.new_from_file("file.png"))
win.add(img)
win.show_all()
Gtk.main()
I haven't tried it yet, but in GTK 4 you should be able to use Gtk.Picture to get the same effect without a custom widget.
Upvotes: 1