#!/usr/bin/python
import gtk.glade
import gtk
import gtk.gdk
import os
import stat
import zipfile
import Image
import StringIO
from decimal import *
import gc

VERSION="0.02"

ARC_EXT=['zip', 'cbz']
IMG_EXT=['png', 'jpg']
MAX_RESIZE = "4"
MIN_RESIZE = ".25"
ZOOM_FACTOR = ".25"
RESIZE_RATIO = "0"
WIN_PATH="pycomic.glade"
NIX_PATH='/usr/share/pycomic/pycomic.glade'

class ComicGUI:
    def __init__(self):
        
        self.xml = gtk.glade.XML(WIN_PATH)

        # call our function helpers
        self.get_widgets()
        self.init_bools()
        self.build_gui()
        self.connect_signal_handlers()
        self.build_key_dict()
        # enable garbage collecting (pixbufloader doesn't collect itself, ya know)
        gc.enable()
        return

# Init Functions
    def connect_signal_handlers(self):
        dic = { "on_window1_destroy"    : self.delete_event, 
                "on_quit_clicked"       : self.delete_event,
                "on_previous_clicked"   : self.prev_event,  
                "on_next_clicked"       : self.next_event,
                "on_zoomin_clicked"     : self.zoomin_event,
                "on_zoomout_clicked"    : self.zoomout_event, 
                "on_bestfit_clicked"    : self.bestfit_event,
                "on_autoresize_toggled" : self.autoresize_event,
                "on_fullscreen_clicked" : self.fullscreen_event,
                "on_normalsize_clicked" : self.normalsize_event,
                "on_show_clicked"       : self.hide_event,
                "on_keypress_event"   : self.on_key_press }
        self.xml.signal_autoconnect(dic)
        return
    
    def build_key_dict(self):
        self.key_dict = { 'right'        : self.next_event,
                          'left'         : self.prev_event,
                          'r'            : self.refresh,
                          'q'            : self.delete_event,
                          'backspace'    : self.back,
                          'minus'        : self.zoomout_event,
                          'plus'         : self.zoomin_event,
                          'f'            : self.fullscreen_event,
                          'h'            : self.hide_event }
        return

    def init_bools(self):
        self.archive_opened = False
        self.fullscreened = False
        self.hidden = False
        self.resize_ratio = Decimal(RESIZE_RATIO)
        self.auto_resize = self.autosize_toggle.get_active()

    def build_gui(self):
        # Build the filebrowser
        self.treeview1 = self.xml.get_widget("treeview1")
        self.treeselection1 = self.treeview1.get_selection()
        self.liststore1 = gtk.ListStore(str, str)
        self.column_names = ['Name'] 
        self.tvcolumn = [None] * len(self.column_names)
        cellpb = gtk.CellRendererPixbuf()
        self.tvcolumn = gtk.TreeViewColumn(self.column_names[0])
        self.tvcolumn.pack_start(cellpb, False)
        self.tvcolumn.set_cell_data_func(cellpb, self.file_pixbuf)
        cell = gtk.CellRendererText()
        self.tvcolumn.pack_start(cell, True)
        self.tvcolumn.set_cell_data_func(cell, self.file_name)
        self.treeview1.append_column(self.tvcolumn)
        self.treeview1.connect('row-activated', self.open_file)
        listmodel = self.read_dir(os.getcwd())
        self.treeview1.set_model(listmodel) 
        # Hide our show window
        self.show_toolbar.hide()
        return

    def get_widgets(self):
        self.autosize_toggle = self.xml.get_widget("autoresize")
        self.window = self.xml.get_widget("window1")
        self.show_toolbar = self.xml.get_widget("toolbar2")
        self.toolbar = self.xml.get_widget("toolbar1")
        self.filebrowser = self.xml.get_widget("scrolledwindow1")
        self.image1 = self.xml.get_widget("image1")
        self.viewport = self.xml.get_widget("viewport1")
        self.scrolledwindow = self.xml.get_widget("scrolledwindow2")
        return


# FILESYSTEM FUNCTIONS

    def open_file(self, treeview, path, column):
        model = treeview.get_model()
        iter = model.get_iter(path)
        self.row = model.get_path(iter)[0]
        value = model.get_value(iter, 0)
        if not self.archive_opened:
            filename = os.path.join(self.dirname, value)
            filestat = os.stat(filename)
            if stat.S_ISDIR(filestat.st_mode):
                self.archive_opened = False
                new_model = self.read_dir(filename)
                treeview.set_model(new_model)
            else:
                self.archive_opened = True
                self.archive_name = value
                self.archive_selected = self.row
                self.row = 1
                new_model = self.read_archive(filename)
                treeview.set_model(new_model)
                self.open_image_by_row(self.row)
        else:
            if value == '..':
                self.archive_opened = False
                new_model = self.read_dir(self.dirname)
                treeview.set_model(new_model)
                self.select_archive(self.archive_selected, self.archive_name)
                del self.archive
            else:
                self.set_image(value, False)
        return
    
    def get_ext(self, filename, valid_extensions):
        if not self.archive_opened:
            filepath = os.path.join(self.dirname, filename)
            filestat = os.stat(filepath)
            if stat.S_ISDIR(filestat.st_mode):
                    return True
            else:
                if filename.rfind('.') > 0:
                    ext = filename[filename.rfind('.')+1:]
                    if not ext.lower() in valid_extensions:
                        return False
                    else:
                        return True
                return False
        else:
            if filename[filename.rfind('.')+1:].lower() in valid_extensions:
                return True
        return False
        

    def read_dir(self, dname=None):
        if not dname:
            self.dirname = os.path.expanduser('~')
        else:
            self.dirname = os.path.abspath(dname)
        files = [f for f in os.listdir(self.dirname) if f[0] <> '.' and self.get_ext(f, ARC_EXT)]
        files.sort()
        files = ['..'] + files
        listmodel = gtk.ListStore(object)
        for f in files:
            listmodel.append([f])
        self.tvcolumn.set_title(self.dirname)
        return listmodel
    
    def read_archive(self, aname):
        self.archive = zipfile.ZipFile(os.path.join(self.dirname, aname))
        self.archive_contents = self.archive.namelist() 
        files = ['..'] + self.archive_contents
        files.sort()
        listmodel = gtk.ListStore(object)
        for f in files:
            if self.get_ext(f, IMG_EXT) or f == '..':
                listmodel.append([f])
        self.tvcolumn.set_title(aname)
        return listmodel

# IMAGE FUNCTIONS

    def set_image(self, value, zoom):
        if value == '..':
            return
        image_fb = StringIO.StringIO(self.archive.read(value))
        image = self.img_resize(image_fb, zoom)
        image_fb.close()
        adjustment = gtk.Adjustment(0)
        self.scrolledwindow.set_vadjustment(adjustment)
        pixbuf = self.get_pixbuf_from_pil(image)
        self.image1.set_from_pixbuf(pixbuf)
        gc.collect()

    def get_pixbuf_from_pil(self, image):
        image_file_buffer = StringIO.StringIO()
        # some PNG files aren't in the RGB format, which makes it hard to save them
        # as PPM files
        if image.mode != 'RGB':
            timage = image.convert('RGB')
            image = timage
        image.save(image_file_buffer, 'ppm')
        image_file_buffer.seek(0)
        gdk_buffer = gtk.gdk.PixbufLoader('pnm')
        gdk_buffer.write(image_file_buffer.read())
        gdk_buffer.close()
        image_file_buffer.close()
        pixbuf = gdk_buffer.get_pixbuf()
        return pixbuf
    
    def img_resize(self, image, zoom):
        resizable = Image.open(image)
        allocated = self.viewport.get_allocation()
        ix, iy = resizable.size
        if self.auto_resize and not zoom:
            wx, wy = allocated[2], allocated[3]
            height_ratio = Decimal(wy)/Decimal(iy)
            width_ratio = Decimal(wx)/Decimal(ix)
            self.resize_ratio = min(height_ratio, width_ratio)
        ny = (Decimal(iy) * self.resize_ratio).to_integral()
        nx = (Decimal(ix) * self.resize_ratio).to_integral()
        if self.resize_ratio != 1:
            resized = resizable.resize((nx, ny))
        else:
            resized = resizable
        return resized
    
    def open_image_by_row(self, row):
        model = self.treeview1.get_model()
        path = (row, )
        try:
            iter = model.get_iter(path)
        except:
            return False
        value = model.get_value(iter, 0)
        self.treeselection1.select_path(path)
        self.set_image(value, False)
        return True


    def reload_image(self, zoom):
        if self.archive_opened:
            model = self.treeview1.get_model()
            path = (self.row, )
            try:
                iter = model.get_iter(path)
            except:
                return
            value = model.get_value(iter, 0)
            self.set_image(value, zoom)

# UI FUNCTIONS

# leave the archive
    def back(self, obj=None):
        if self.archive_opened:
            self.archive_opened = False
            new_model = self.read_dir(self.dirname)
            self.treeview1.set_model(new_model)
            self.treeselection1.select_path((self.archive_selected))
        return

    def file_pixbuf(self, column, cell, model, iter):
        if self.archive_opened:
            cell.set_property('stock-id', gtk.STOCK_NEW)
            return
        filename = os.path.join(self.dirname, model.get_value(iter, 0))
        filestat = os.stat(filename)
        if stat.S_ISDIR(filestat.st_mode):
            pb = gtk.STOCK_OPEN
        else:
            pb = gtk.STOCK_NEW
        cell.set_property('stock-id', pb)
        return

    def file_name(self, column, cell, model, iter):
        cell.set_property('text', model.get_value(iter, 0))
        return

    def fullscreen(self):
        if self.fullscreened:
            self.window.unfullscreen()
        else:
            self.window.fullscreen()
        self.fullscreened = not self.fullscreened
        return

    def refresh(self, obj):
        if self.archive_opened:
            self.bestfit_event(obj)
        else:
            listmodel = self.read_dir(self.dirname)
            self.treeview1.set_model(listmodel)

    def hide_toggle(self):
        if self.hidden:
            self.toolbar.show()
            self.filebrowser.show()
            self.show_toolbar.hide()
        else:
            self.toolbar.hide()
            self.filebrowser.hide()
            self.show_toolbar.show()
        self.hidden = not self.hidden
        return

# highlights the archive we just went into when we exit it.
    def select_archive(self, row, a_name):
        model = self.treeview1.get_model()
        iter = model.get_iter((self.row, ))
        value = model.get_value(iter, 0)
        if a_name == value:
            self.treeselection1.select_path((self.row, ))
            return
        iter = model.get_iter_first()
        self.row = 0
        while iter != None:
            iter = model.iter_next(iter)
            if a_name == value:
                self.treeselection1.select_path((self.row, ))
                return
            value = model.get_value(iter, 0)
            self.row = self.row + 1
        return

# Signal Handlers
    def delete_event(self, obj):
        gtk.main_quit()

    def prev_event(self, obj):
        if self.archive_opened:
            if self.row > -1: 
                self.row = self.row - 1
                if not self.open_image_by_row(self.row):
                    self.row = self.row + 1
        return                
                    

    def next_event(self, obj):
        if self.archive_opened:
            if self.row > -1: 
                self.row = self.row + 1
                if not self.open_image_by_row(self.row):
                    self.row = self.row - 1
        return

    def zoomin_event(self, obj):
        if self.resize_ratio < Decimal(MAX_RESIZE):
            self.resize_ratio = self.resize_ratio + Decimal(ZOOM_FACTOR)
            self.reload_image(True)
        return

    def zoomout_event(self, obj):
        if self.resize_ratio > Decimal(MIN_RESIZE):
            self.resize_ratio = self.resize_ratio - Decimal(ZOOM_FACTOR)
            self.reload_image(True)
        return

    def bestfit_event(self, obj):
        autoresize = self.auto_resize
        self.auto_resize = True
        self.reload_image(False)
        self.auto_resize = autoresize
        return

    def autoresize_event(self, obj):
        self.auto_resize = not self.auto_resize
        return

    def normalsize_event(self, obj):
        self.resize_ratio = 1
        self.reload_image(True)
        return

    def fullscreen_event(self, obj):
        self.fullscreen()
        return

    def hide_event(self, obj):
        self.hide_toggle()

    def on_key_press(self, obj, event):
        keyname = gtk.gdk.keyval_name(event.keyval).lower()
        if self.key_dict.has_key(keyname):
            apply(self.key_dict[keyname], (obj, ))
            return True
        return
    
def main():
    gtk.main()
if __name__ == "__main__":
    driver = ComicGUI()
    main()

