Tim Evans wrote:

It depends on what you need to do, and on what you are comfortable using. Threads are good if what you are doing is going to take a long time and can't be divided up into small chunks that can be executed in an idle callback. Even if something can be done in multiple idle handlers I find that it is *sometimes* easier to use a separate thread.

The primary disadvantage of threads is that synchronisation problems and race conditions can get very complex if you aren't careful. Use Python's 'Queue' module for everything; if your algorithm requires something more complicated than Queue then change your algorithm ;-)

I have attached the new incarnation of the IconView. It now implements the scrolling, too. Unfortunately, if I scroll too quickly, the application locks. One solution (that I don't like too much) is to change the update policy for the scrollbar.


Any ideas on why is this lock happening ?

TIA,

Ionutz
import sys

if sys.platform.startswith("win"):
    # Fetchs gtk2 path from registry
    import _winreg
    import msvcrt
    try:
        k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "Software\\GTK\\2.0")
    except EnvironmentError:
        print "You must install the Gtk+ 2.2 Runtime Environment to run this program"
        while not msvcrt.kbhit():
            pass
        sys.exit(1)
    else:    
        gtkdir = _winreg.QueryValueEx(k, "Path")
        import os
        # we must make sure the gtk2 path is the first thing in the path
        # otherwise, we can get errors if the system finds other libs with
        # the same name in the path...
        os.environ['PATH'] = "%s/lib;%s/bin;" % (gtkdir[0], gtkdir[0]) + 
os.environ['PATH']

import pygtk
pygtk.require ('2.0')
import gtk

import threading
import array

# ------------- ADT part -----------------

class Fifo:
    def __init__(self):
        self.__data = array.array('l')

    def peek(self):
        return self.__data[-1]

    def empty(self):
        return len(self.__data) == 0

    def push(self, item):
        self.__data.insert(0, item)

    def pop(self):
        return self.__data.pop()
    
    def clear(self):
        while len(self.__data) != 0:
            self.__data.pop()

class Stack:
    def __init__(self):
        self.__data = array.array('l')
    
    def peek(self):
        return self.__data[-1]

    def empty(self):
        return len(self.__data) == 0

    def push(self, item):
        self.__data.append(item)

    def pop(self):
        return self.__data.pop()
    
    def clear(self):
        while len(self.__data) != 0:
            self.__data.pop()

# ------------- Icon View part -----------------

class IconView(gtk.HBox):
    def __init__(self):
        gtk.HBox.__init__(self, gtk.FALSE, 0)
        self.__area = gtk.DrawingArea()
        self.pack_start(self.__area, gtk.TRUE, gtk.TRUE, 0)
        self.__area.show()

        self.__scrollbar = gtk.VScrollbar(adjustment=None)
        self.pack_start(self.__scrollbar, gtk.FALSE, gtk.FALSE, 0)
        self.__scrollbar.show()
        
        self.__selected = None # nothing selected

        self.__items = 0 # number of items
        self.__current_row = 0
        self.__rows = 1 # number of rows
        self.__cols = 1 # number of columns
        self.__item_width = 1 # item width
        self.__item_height = 1 # item height
        self.__item_border = 0
        self.__show_labels = True

        # threading
        self.__icon_lock = threading.Lock()
        self.__icon_event = threading.Event()
        self.__icon_event.clear()
        self.__icon_queue = Fifo()
        self.__icon_thread = threading.Thread(None,
                                               self.item_thread,
                                               'label',
                                               (self.__icon_queue,
                                                self.__icon_event,
                                                self.__icon_lock,
                                                self.get_icon,
                                                self.display_icon,
                                                'label'))
        self.__icon_thread.start()

        # we need this, otherwise the whole app stops responding
        # self.__scrollbar.set_update_policy(gtk.UPDATE_DELAYED)
        
        self.__adjustment = self.__scrollbar.get_adjustment()
        self.__adjustment.set_all(0,0,0,0,0,0)
        self.__adjustment.connect("value_changed", self.on_scroll)
        
        self.__area.connect("configure_event", self.on_configure)
        self.__area.connect("expose-event", self.on_expose)
        self.__area.connect("unrealize", self.on_unrealize)

    def on_scroll(self, adj):
        current_row = int(adj.value)
        if current_row != self.__current_row:
            self.__area.queue_draw()
        
    def on_unrealize(self, area, data = None):
        self.__icon_lock.acquire()
        self.__icon_queue.clear()
        self.__icon_queue.push(-1)
        self.__icon_lock.release()
        self.__icon_event.set()

    def item_thread(self, queue, event, lock, get_item, display_item, name):
        while True:
            lock.acquire()
            while queue.empty():
                lock.release()
                event.wait()
                lock.acquire()
            event.clear()
            index = queue.peek()
            lock.release()
            if index == -1:
                return # we've been signalized to exit the thread
            item = get_item(index)
            lock.acquire()
            if not queue.empty():
                if index == queue.peek():
                    display_item(index, item)
                    queue.pop()
            lock.release()

    def get_label(self, index):
        return ('Item %d' % index)

    def set_layout_label(self, layout, label, max_width):
        layout.set_text(label)
        width, height = layout.get_pixel_size()
        i = -3
        while width > max_width:
            label0 = label[:i] + '...'
            layout.set_text(label0)
            width, height = layout.get_pixel_size()
            i -= 1
        return layout
        
    def display_label(self, index, label):
        row = index / self.__cols - self.__current_row
        col = index % self.__cols
        x = col * self.__item_width + self.__item_border
        y = row * self.__item_height + self.__item_border
        self.__window.draw_layout(self.__gc,
                                  x,
                                  y,
                                  self.set_layout_label(self.__pangolayout,
                                                         label,
                                                         self.__item_width - 2 * 
self.__item_border))

    def get_icon(self, index):
        return ('Icon %d' % index)

    def display_icon(self, index, icon):
        row = index / self.__cols
        col = index % self.__cols
        x = col * self.__item_width + self.__item_border
        y = row * self.__item_height + self.__item_border
        gtk.threads_enter()
        #print '-- display icon "%s"' % icon
        gtk.threads_leave()

    def on_expose(self, area, event):
        rect = event.area
        self.__pangolayout = area.create_pango_layout("")
        self.__style = area.get_style()
        self.__window = area.window
        self.__gc = self.__style.fg_gc[gtk.STATE_NORMAL]

        # determin the area that needs to be drawn
        col1 = rect.x / self.__item_width
        col2 = min((rect.x + rect.width) / self.__item_width + 1, self.__cols)
        row1 = rect.y / self.__item_height
        row2 = min((rect.y + rect.height) / self.__item_height + 1, self.__rows) # we 
don't let row2 be more than the total number of rows

        self.__icon_lock.acquire()
        self.__icon_queue.clear()

        self.__current_row = int(self.__adjustment.value)
        if col1 == 0 and col2 == 0:
            for row in range(row1, row2):
                if row < self.__items: # the col is always 0
                    index = row + self.__current_row
                    self.__icon_queue.push(index)
                    # labels
                    label = self.get_label(index)
                    self.display_label(index, label)
        else:
            for row in range(row1, row2):
                for col in range(col1, col2):
                    index = (row + self.__current_row) * self.__cols + col
                    if index < self.__items:
                        self.__icon_queue.push(index)
                        # labels
                        label = self.get_label(index)
                        self.display_label(index, label)

        self.__icon_lock.release()
        self.__icon_event.set()

        return gtk.TRUE

    def on_configure(self, widget, event, data=None):
        width = event.width
        height = event.height
        self.__cols = width / self.__item_width
        if self.__cols <= 0:
            self.__cols = 1 # we can't have less than 1 column !
        self.__rows = self.__items / self.__cols
        while self.__rows * self.__cols < self.__items:
            self.__rows += 1

        page_size = int(height / self.__item_height)
        if page_size < 1:
            page_size = 1
        if page_size > self.__rows:
            page_size = self._rows
        self.__adjustment.set_all(0, 0, self.__rows, 1, page_size, page_size)

    def set_all(self, items, width, height, border, show_labels = True):
        # set all properties of the icon view with one call
        # items - number of items fromthe view
        # width - width of one item
        # height - height of one item
        # border - the border arround the image / text
        # show_labels - if true, print some labels under the item
        if items < 0:
            items = 0
        if width < 1:
            width = 1
        if height < 1:
            height = 1
        if border < 0:
            border = 0

        self.__items = items
        self.__item_width = width
        self.__item_height = height
        self.__item_border = border
        self.__show_labels = show_labels

        # redraw all items now ...

    def get_selected(self):
        return self.__selected

    def set_selected(self, value):
        self.__selected = value

    def get_rows(self):
        return self.__rows

    def get_cols(self):
        return self.__cols

    selected = property(get_selected, set_selected, None, 'selected item')
    rows = property(get_rows, None, None, 'number of rows')
    cols = property(get_cols, None, None, 'number of rows')
    
# ------------- Test part -----------------

class Test:
    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

        # You should always remember to connect the delete_event signal
        # to the main window. This is very important for proper intuitive
        # behavior
        self.window.connect("delete_event", self.delete_event)

        # Create an icon-view and adds it to the window
        icon_view = IconView()
        icon_view.set_all(300, 69, 128, 5)
        #icon_view.set_size_request(400, 300) # make the icon view a bit bigger
        self.window.add(icon_view)
        icon_view.show()
        
        # the last thing we do is showing the window
        self.window.show()

    def main(self):
        gtk.main()

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return gtk.FALSE

if __name__ == "__main__":
    gtk.threads_init()
    gtk.threads_enter()
    test = Test()
    test.main()
    gtk.threads_leave()
_______________________________________________
pygtk mailing list   [EMAIL PROTECTED]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/

Reply via email to