Search code examples
python-3.xgtk3pycairodrawingarea

Resizing a Gtk.DrawingArea in PyGtk3


Please run the code below to get something like this:

enter image description here

Basically, I've set up a Gtk.DrawingArea, which is inside a Gtk.Viewport, which is also in a Gtk.Scrolledwindow. In said DrawingArea I draw an image, and with the 2 buttons you can scale said image.

# -*- encoding: utf-8 -*-

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf


class MyWindow(Gtk.Window):

    def __init__(self):

        Gtk.Window.__init__(self, title="DrawingTool")
        self.set_default_size(800, 600)

        # The ratio
        self.ratio = 1.

        # Image
        filename = "image.jpg"
        self.original_image = GdkPixbuf.Pixbuf.new_from_file(filename)
        self.displayed_image = GdkPixbuf.Pixbuf.new_from_file(filename)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        # Zoom buttons
        self.button_zoom_in = Gtk.Button(label="Zoom-In")
        self.button_zoom_out = Gtk.Button(label="Zoom-Out")      
        # |ScrolledWindow
        # |-> Viewport
        # |--> DrawingArea 
        scrolledwindow = Gtk.ScrolledWindow()
        viewport = Gtk.Viewport()
        self.drawing_area = Gtk.DrawingArea()
        self.drawing_area.set_size_request(
                              self.displayed_image.get_width(), self.displayed_image.get_height())
        self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK)

        # Pack
        viewport.add(self.drawing_area)
        scrolledwindow.add(viewport)
        box.pack_start(self.button_zoom_in, False, True, 0)
        box.pack_start(self.button_zoom_out, False, True, 0)
        box.pack_start(scrolledwindow, True, True, 0)
        self.add(box)

        # Connect
        self.connect("destroy", Gtk.main_quit)
        self.button_zoom_in.connect("clicked", self.on_button_zoom_in_clicked)
        self.button_zoom_out.connect("clicked", self.on_button_zoom_out_clicked)
        self.drawing_area.connect("enter-notify-event", self.on_drawing_area_mouse_enter)
        self.drawing_area.connect("leave-notify-event", self.on_drawing_area_mouse_leave)
        self.drawing_area.connect("motion-notify-event", self.on_drawing_area_mouse_motion)
        self.drawing_area.connect("draw", self.on_drawing_area_draw)

        self.show_all()

    def on_button_zoom_in_clicked(self, widget):
        self.ratio += 0.1
        self.scale_image()
        self.drawing_area.queue_draw()

    def on_button_zoom_out_clicked(self, widget):
        self.ratio -= 0.1
        self.scale_image()
        self.drawing_area.queue_draw()

    def scale_image(self):
        self.displayed_image = self.original_image.scale_simple(self.original_image.get_width() * self.ratio, 
                                   self.original_image.get_height() * self.ratio, 2)

    def on_drawing_area_draw(self, drawable, cairo_context):
        pixbuf = self.displayed_image           
        self.drawing_area.set_size_request(pixbuf.get_width(), pixbuf.get_height())
        Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0)
        cairo_context.paint()

    def on_drawing_area_mouse_enter(self, widget, event):
        print("In - DrawingArea")

    def on_drawing_area_mouse_leave(self, widget, event):
        print("Out - DrawingArea")

    def on_drawing_area_mouse_motion(self, widget, event):

        (x, y) = int(event.x), int(event.y)
        offset = ( (y*self.displayed_image.get_rowstride()) + 
                   (x*self.displayed_image.get_n_channels()) )
        pixel_intensity = self.displayed_image.get_pixels()[offset]
        print("(" + str(x) + ", " + str(y) + ") = " + str(pixel_intensity))



MyWindow()
Gtk.main()

Run the application. I've also set up some callbacks so when you hover the mouse pointer over the image, you get to know:

i) When you enter the DrawingArea

ii) When you leave the DrawingArea

iii) The (x, y) at the DrawingArea and image pixel intensity

The problem I'm facing is, when you Zoom-out enough times, the image will look like this:

enter image description here

However, the 'mouse enter' and 'mouse leave' signals, aswell the 'mouse motion' one are sent like the DrawingArea still has the same size it was created with. Please hover the mouse over the image and outside the image but inside the DrawingArea to see the output in your terminal.

I would like the signals not to send themselves when hovering outside the image. This is, adapt the DrawingArea size to the displayed image size, if possible.


Solution

  • I've used gtk_window_resize() as it is mentioned here: Cannot reduce size of gtk window programatically

    def on_drawing_area_draw(self, drawable, cairo_context):
            pixbuf = self.displayed_image
            # New line. Get DrawingArea's window and resize it.
            drawable.get_window().resize(pixbuf.get_width(), pixbuf.get_height())           
            drawable.set_size_request(pixbuf.get_width(), pixbuf.get_height())
            Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0)
            cairo_context.paint()