performancegtkpygtkcairorubber-band

Cairo + rubber band selection: gui very very slow


I'm working on an application written in Cairo + Gtk. Please note that, due to retrocompatibility issues, I am forced to use Python as programming language, PyGTK as wrapper, and GTK libraries, v.2.24. No chance to use C/C++ and/or GTK3!

My app need to (re)draw a big amount of data on screen for each invocation of expose method(obviously).

I would simply give to users a chance to manually select some objects previously drawn with Cairo. Since I'm drawing on gtk.DrawingAreas, it seems that I must manually implement the rubber band selection functionality.

This is the question:

Is there any way to redraw the rubber band rectangle at every mouse move, avoiding to redraw all others objects on screen?

I would redraw only the selection rectangle for performance reasons.

Due to the big amount of graphical objects, my GUI is terribly slow. Unfortunately, despite several attempts, I have no choice: redraw all or redraw anything!

First thing that came in my mind: is there any way to overlay an intermediate level between the DrawingArea with most of data and the mouse cursor? by calling queue_draw_area() function there aren't performance gains.

A simple, self containing code example below: obviously, in this case Ionly use cairo to draw extremely simple graphic objects.

import gtk
from gtk import gdk

class Canvas(gtk.DrawingArea):

    # the list of points is passed as argument

    def __init__(self, points):
        super(Canvas, self).__init__()
        self.points = points
        self.set_size_request(400, 400)

        # Coordinates of the left-top angle of the selection rect
        self.startPoint = None

        self.endPoint = None

        # Pixmap to drawing rubber band selection
        self.pixmap = None

        self.connect("expose_event", self.expose_handler)
        self.connect("motion_notify_event", self.mouseMove_handler)
        self.set_events(gtk.gdk.EXPOSURE_MASK
                            | gtk.gdk.LEAVE_NOTIFY_MASK
                            | gtk.gdk.BUTTON_PRESS_MASK
                            | gtk.gdk.POINTER_MOTION_MASK
                            | gtk.gdk.POINTER_MOTION_HINT_MASK)

    # Method to paint lines and/or rubberband on screen

    def expose_handler(self, widget, event):

        rect = widget.get_allocation()
        w = rect.width
        h = rect.height
        ctx = widget.window.cairo_create()
        ctx.set_line_width(7)
        ctx.set_source_rgb(255, 0, 0)
        ctx.save()

        for i in range(0, len(self.points)):
            currPoint = self.points[i]
            currX = float(currPoint[0])
            currY = float(currPoint[1])
            nextIndex = i + 1
            if (nextIndex == len(self.points)):
                continue
            nextPoint = self.points[nextIndex]
            nextX = float(nextPoint[0])
            nextY = float(nextPoint[1])
            ctx.move_to(currX, currY)
            ctx.line_to(nextX, nextY)
        ctx.restore()
        ctx.close_path()
        ctx.stroke()

        # rubber band
        if self.pixmap != None:
            width = self.endPoint[0] - self.startPoint[0]
            height = self.endPoint[1] - self.startPoint[1]
            if width < 0 or height < 0:
                tempEndPoint = self.endPoint
                self.endPoint = self.startPoint
                self.startPoint = tempEndPoint

            height = self.endPoint[1] - self.startPoint[1]
            width = self.endPoint[0] - self.startPoint[0]
            widget.window.draw_drawable(widget.get_style().fg_gc[gtk.STATE_NORMAL], self.pixmap, self.startPoint[0], self.startPoint[1], self.startPoint[0], self.startPoint[1], abs(width), abs(height))

    def mouseMove_handler(self, widget, event):
        x, y, state = event.window.get_pointer()
        if (state & gtk.gdk.BUTTON1_MASK):
            if (state & gtk.gdk.CONTROL_MASK):
                if self.startPoint == None:
                    self.startPoint = (x,y)
                    self.endPoint = (x,y)
                else:
                    self.endPoint = (x,y)
                tempPixmap = gtk.gdk.Pixmap(widget.window, 400, 400)

                height = self.endPoint[1] - self.startPoint[1]
                width = self.endPoint[0] - self.startPoint[0]

                gc = self.window.new_gc()
                gc.set_foreground(self.get_colormap().alloc_color("#FF8000"))

                gc.fill = gtk.gdk.STIPPLED
                tempPixmap.draw_rectangle(gc, True, self.startPoint[0], self.startPoint[1], abs(width), abs(height))

                self.pixmap = tempPixmap
                # widget.queue_draw_area()
                widget.queue_draw()
        else:
            if (self.pixmap != None):
                self.pixmap = None

                # ...do something...

                # widget.queue_draw_area(self.startPoint[0], self.startPoint[1], )
                widget.queue_draw()


li1 = [(20,20), (380,20), (380,380), (20,380)]

window = gtk.Window()
canvas = Canvas(li1)
window.add(canvas)
window.connect("destroy", lambda w: gtk.main_quit())
window.show_all()
gtk.main()

Thanks!

IT


Solution

  • Some general tips when drawing with Gtk+/Cairo:

    Here is a little program I make: rubberband.py. I took the code from a project of mine and added a couple of circles one can select. Hope you can use it as a starting point.