Hi,

Here is my simple icon viewer widget.

It uses a thread to display the icons. I would appreciate any comments/suggestions for possible improvements.

For example, one thing I'm not very comfortable with is that the IconViewer is derived from a HBox. How can I derive it directly from gtk.Widget ? Is there any example on how to do this ?

Another thing I'm not very happy with is the way the icon_thread is signaled to stop. All my other trials produced deadlocks - this one appears to work, but I'm not sure it is the right way to do it ...

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
import string

# ------------- 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_thread_flag = True # when this becomes false, the threads ends
        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.icon_thread,
                                               'label',
                                               (self.__icon_queue,
                                                self.__icon_event,
                                                self.__icon_lock,
                                                self.get_icon,
                                                self.display_icon))
        self.__icon_thread.start()

        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_thread_flag = False
        self.__icon_event.set() # make sure the thread is not blocked
        return gtk.FALSE

    def icon_thread(self, queue, event, lock, get_item, display_item):
        while self.__icon_thread_flag:
            if lock.acquire(False):
                while queue.empty():
                    lock.release()
                    event.wait()
                    if not self.__icon_thread_flag:
                        return
                    lock.acquire()
                event.clear()
                index = queue.peek()
                lock.release()
                (item, width, height) = get_item(index,
                                                 self.__item_width - 2 * 
self.__item_border,
                                                 self.__item_height - 4 * 
self.__item_border)
                if lock.acquire(False):
                    if not queue.empty():
                        if index == queue.peek():
                            display_item(index, item, width, height)
                            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, width, height)
        
    def display_label(self, index, label):
        (layout, width, height) = self.set_layout_label(self.__pangolayout,
                                                         label,
                                                         self.__item_width - 2 * 
self.__item_border)
        row = index / self.__cols - self.__current_row
        col = index % self.__cols
        x = col * self.__item_width + self.__item_border
        y = (row + 1) * self.__item_height - self.__item_border - height
        self.__window.draw_layout(self.__gc,
                                  x,
                                  y,
                                  layout)

    def get_icon(self, index, width, height):
        b = width*3*height*['\0']
        for i in range(width):
            for j in range(height):
                b[3*height*i+3*j] = chr((index % 32)*4)
                b[3*height*i+3*j+1] = chr((index % 32)*4)
                b[3*height*i+3*j+2] = chr((index % 32)*4)
        buff = string.join(b, '')
        return (buff, width, height)

    def display_icon(self, index, icon, width, height):
        row = index / self.__cols - self.__current_row
        col = index % self.__cols
        x = col * self.__item_width + self.__item_border + (width - self.__item_width) 
/ 2
        y = row * self.__item_height + self.__item_border + (height - 
self.__item_height) / 2

        gtk.threads_enter()
        self.__window.draw_rgb_image(self.__gc, x, y, width, height,
                                        gtk.gdk.RGB_DITHER_NONE, 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)
        return gtk.FALSE

    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):
        self.window.hide()
        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