#!/usr/bin/env python
#-*- coding: utf-8 -*-
# memaker.py - A gtk based avatar creation program.
# Copyright 2008 The MeMaker Project
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. See the file LICENSE.
# If not, see <http://www.gnu.org/licenses/>.
import sys , os, fileinput, rsvg
from libmemaker.motor import *
import pygtk
import gtk
import gtk.glade
import cairo
#import StringIO
import pynotify
import webbrowser
try:
    import Image, ImageChops
except ImportError:
    print 'Couldnt find PIL,  install `python-imaging` for autogenerated thumbnails.' #Could be a dialog
    print 'Continuing without the feature...'
    from libmemaker.image_loader_dummy import *
else:
    from libmemaker.image_loader import *
os.chdir(os.sys.path[0])

def mkdirCheck(newdir):
    """Check directoris if they exist and creat them if they don't"""
    if os.path.isdir(newdir):
        pass
    elif os.path.isfile(newdir):
        raise OSError("a file with the same name as the desired " \
                      "dir, '%s', already exists." % newdir)
    else:
        head, tail = os.path.split(newdir)
        if head and not os.path.isdir(head):
            print "A required Directory in your Home Directory was needed.... making:", head
            os.mkdir(head)
        if tail:
            print "A required Directory in your Home Directory was needed.... making:", newdir
            os.mkdir(newdir)
class MeMakerGui:
    
    class featuresObject():
        pass
        
    class runningHeads():
        pass
        
    def __init__(self):
        self.featureList = ["Head","Hair","Eye","Mouth","Ear","Glasses","Nose","Eyebrow","Hat","Accessory","Beard"]
        gladefile = "/usr/share/memaker/memaker.glade"
        self.windowname = "meMaker"
        self.currentFace = "Unset"
        self.meMakerWin = gtk.glade.XML( gladefile, self.windowname )
        #Create the default directory structure if not there now....
        locationMkDir = os.path.expanduser("~/.MeMaker/themes")
        mkdirCheck(locationMkDir)
        #Make a directory for the cache if it doesn't exist.
        mkdirCheck(os.path.expanduser("~/.MeMaker/cache"))
        #Create the window system for memaker
        windowMain = self.meMakerWin.get_widget("meMaker")
        windowMain.set_app_paintable(True)
        self.themesIHad = [] #This is for remembering which themes we had.
        #Getting and connecting the widgets
        avatarPicture = self.meMakerWin.get_widget("imageAvatar")
        loadAbout = self.meMakerWin.get_widget("buttonAbout").connect('clicked', self.loadAbout)
        avatarSaveAs = self.meMakerWin.get_widget("buttonSaveAs")
        resetAvatar = self.meMakerWin.get_widget("buttonReset").connect('clicked', self.avatarReset)
        buttonUp = self.meMakerWin.get_widget("buttonUp").connect('clicked', self.arrowUpClicked)
        arrowDown = self.meMakerWin.get_widget("buttonDown").connect('clicked', self.arrowDownClicked)
        buttonLink = self.meMakerWin.get_widget("linkbuttonDownload").connect('clicked', self.linkClicked)
        quitMeMaker = self.meMakerWin.get_widget("meMaker").connect('destroy', self.saveAndQuit)
        #Adding the save as combobox
        comboboxSaveAs = self.meMakerWin.get_widget("comboboxSaveAs")
        comboboxSaveAs.set_active(0)
        comboboxSaveAs.connect('changed',self.saveAsChanged)
        
        #Adding the buttons for removing features...
        for feat in self.featureList:
            buttons = self.meMakerWin.get_widget("buttonRemove"+feat)
            buttons.connect('clicked', self.removeFeature)
        #Connect all feature iconviews with there proper button presses.
        self.featuresMade = {}
        for feat in self.featureList:
            tempvar = self.featuresObject()
            tempvar.model = gtk.ListStore(str, gtk.gdk.Pixbuf)
            tempvar.iconview = self.meMakerWin.get_widget('iconview'+feat)
            tempvar.iconview.set_model(tempvar.model)
            tempvar.iconview.set_pixbuf_column(1)
            tempvar.iconview.connect('selection-changed', self.featureClicked, tempvar.model)
            self.featuresMade[feat] = tempvar
        #Connect to the combobox and add all the themes to the combobox
        comboboxThemePicker = self.meMakerWin.get_widget("comboboxThemes")
        listStore = gtk.ListStore(gtk.gdk.Pixbuf, str)
        comboboxThemePicker.set_model(listStore)
        px = gtk.CellRendererPixbuf()
        text = gtk.CellRendererText()
        comboboxThemePicker.pack_start(px, False)
        comboboxThemePicker.pack_start(text, False)
        comboboxThemePicker.add_attribute(px, "pixbuf", 0)
        comboboxThemePicker.add_attribute(text, "text", 1)
        locations = ["/usr/share/memaker/themes/",os.path.expanduser("~/.MeMaker/themes/")] #The areas that will load themes
        themesList = []
        themesListB = []
        for location in locations:
            for items in os.listdir(location):
                if os.path.isdir(location+items):
                    themesList.append(items)
                    themesListB.append(location + items + "/")
        #TODO, clean up this very dirty hack.  My gosh!
        #Creating the list of themes with their picture in nice combo box
        count = 0
        for items in themesList:
            image = gtk.gdk.pixbuf_new_from_file(themesListB[count] + "/" + items + ".png")
            count = count +1
            listStore.append((image, items))
        #For each theme start up it's object stack and then assign it to the the dictionary for reference
        #The reference is the actually the location of the themes.
        self.loadedHeads =  {}
        for items in themesListB:
            self.loadedHeads[items] = ObjectStack()
        #Try to load the old ObjectStakcs and then see if it works.  If it doesn't load ignore and move on
        try:
            self.loadPickledHead()
        except:
            print "Error found in the pickledHeads file ignoring and moving on..."
        linkbuttonDownload = self.meMakerWin.get_widget("linkbuttonDownload")
        linkbuttonDownload.hide()
        self.avatarPicture = self.meMakerWin.get_widget("imageAvatar")
        self.clearIt = rsvg.Handle("/usr/share/memaker/clearIt.svg")
        self.avatarPicture.set_size_request(400,400)
        self.avatarPicture.connect("expose-event", self.draw_scene)
        #Show the window now so all major changes to the gui are applied first.
        windowMain.show()
        #Now to load the last theme used
        memakerConf = os.path.expanduser("~/.MeMaker/conf.conf")
        try:
            confFile = file(memakerConf, "r")
            themeToLoad = confFile.read()
        except IOError:
            print "No conf.conf file found. Creating a new one with the default theme."
            themeToLoad = "/usr/share/memaker/themes/cocoHead/"
        if os.path.isdir(themeToLoad):
            self.lastThemeLocation = themeToLoad
            self.loadFeatures(themeToLoad)
        else:
            print "Theme to load was not found.  Was it deleted/moved?"
            print "Falling back to default theme..."
            themeToLoad = "/usr/share/memaker/themes/cocoHead/"
            self.lastThemeLocation = themeToLoad
            self.loadFeatures(themeToLoad)
        
        #Even more dirty code, just plain yucky, I am ashamed of myself!
        ###############################################################
        count = 0
        if len(themesList) <= 1:
            comboboxThemePicker.hide()
            linkbuttonDownload.show()
        else:
            linkbuttonDownload.hide()
            for items in themesListB:
                if themeToLoad == items:
                    comboboxThemePicker.set_active(count)
                count = count + 1
            comboboxThemePicker.show()
        ###############################################################
        self.themeLocation = themeToLoad
        comboboxThemePicker.connect('changed',self.themeChanged, themesList, themesListB)
        
    def linkClicked(self, linkButton):
        """
        Open the memaker website in the user's default browser
        """
        webbrowser.open_new("http://memaker.org/themes/")
        
    def saveAsChanged(self, comboboxSaveAs):
        """
        This takes the comboBox fines the text selected and then saves it to the format selected with the correct settings.
        """
        fileType = comboboxSaveAs.get_active_text()
        comboboxSaveAs.set_active(0)
        if self.currentFace.amIEmpty():
            return
        if fileType == "SVG":
            locationSave = self.saveLoadFeatures(gtk.FILE_CHOOSER_ACTION_SAVE, "svg",FILE_EXT = {"Scalable Vector Graphics | SVG":"svg"})
            file = open(locationSave, "w")
            file.write(self.currentFace.printMe())
            return
        if fileType == "PNG":
            locationSave = self.saveLoadFeatures(gtk.FILE_CHOOSER_ACTION_SAVE, "png",FILE_EXT = {"Scalable Vector Graphics | PNG":"png"})
            self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            cr = self.avatarPicture.window.cairo_create()
            self.clearIt.render_cairo(cr)
            pixbuf = self.svgHandle.get_pixbuf()
            pixbuf.save(locationSave, 'png')
            return
        if fileType == "BMP":
            locationSave = self.saveLoadFeatures(gtk.FILE_CHOOSER_ACTION_SAVE, "bmp",FILE_EXT = {"Bitmap Image | BMP":"bmp"})
            self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            cr = self.avatarPicture.window.cairo_create()
            self.clearIt.render_cairo(cr)
            pixbuf = self.svgHandle.get_pixbuf()
            pixbuf.save(locationSave, 'bmp')
            return
        if fileType == "Gnome Avatar":
            self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            cr = self.avatarPicture.window.cairo_create()
            self.clearIt.render_cairo(cr)
            backupFile = os.path.expanduser("~/.Trash/.face")                   #location where a copy of the old .face file will go.
            faceLocation = os.path.expanduser("~/.face")                        #Location of the .face file that will be replaced with the avatar image
            pynotify.init("MeMaker Notify")
            notifier = pynotify.Notification("Profile Updated",
                              "Your Gnome avatar has been updated. \nYour old avatar has been move to the trash.")
            try:                                                                #This may fail
                shutil.move(faceLocation, backupFile)
            except IOError, inst:
                notifier = "No Backup"
            pixbuf = self.svgHandle.get_pixbuf()
            pixbuf = pixbuf.scale_simple(256,256,gtk.gdk.INTERP_BILINEAR)
            pixbufNot = pixbuf
            pixbuf.save(faceLocation, 'png')
            #Use pynotify to inform the user of the change.
            if notifier == "No Backup":
                notifier = pynotify.Notification("Profile Updated",
                              "Your Gnome avatar has been updated.")
            notifier.add_action("undo", "undo", self.testing)
            
            helper = gtk.Button()
            notifier.set_icon_from_pixbuf(pixbuf.scale_simple(50,50,gtk.gdk.INTERP_BILINEAR))
            notifier.show()
            return
        if fileType == "Launchpad Logo":
            locationSave = self.saveLoadFeatures(gtk.FILE_CHOOSER_ACTION_SAVE, "png",FILE_EXT = {"Scalable Vector Graphics | PNG":"png"})
            self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            cr = self.avatarPicture.window.cairo_create()
            self.clearIt.render_cairo(cr)
            pixbuf = self.svgHandle.get_pixbuf()
            pixbuf = pixbuf.scale_simple(64,64,gtk.gdk.INTERP_BILINEAR)
            pixbuf.save(locationSave, 'png')
            return
        if fileType == "Launchpad Mugshot":
            locationSave = self.saveLoadFeatures(gtk.FILE_CHOOSER_ACTION_SAVE, "png",FILE_EXT = {"Scalable Vector Graphics | PNG":"png"})
            self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            cr = self.avatarPicture.window.cairo_create()
            self.clearIt.render_cairo(cr)
            pixbuf = self.svgHandle.get_pixbuf()
            pixbuf = pixbuf.scale_simple(192,192,gtk.gdk.INTERP_BILINEAR)
            pixbuf.save(locationSave, 'png')
            return
        
    def themeChanged(self, comboboxThemePicker, themesList, themesListB):
        """
        Restore the proper theme for extraction
        """
        themeName = comboboxThemePicker.get_active()
        self.themeLocation = themesListB[themeName]
        self.loadFeatures(self.themeLocation)

    def loadFeatures(self, themeLocation = ""):
        """
        Load the features from themeLocation and place it in the iconview for the user to see and click on
        """
        self.currentFace = self.loadedHeads[themeLocation]
        self.applyChanges()
        notebookFeatures = self.meMakerWin.get_widget("notebookFeatures")
        notebookFeatures.set_current_page(0)
        progressbarLoading = self.meMakerWin.get_widget("progressbarLoading")
        progressbarLoading.show()
        comboboxThemePicker = self.meMakerWin.get_widget("comboboxThemes")
        comboboxThemePicker.hide()
        self.featListLoading = []
        for feat in self.featureList:
            self.featuresMade[feat].model.clear()
            self.featListLoading.append({'text':'Loading '+feat+'(s)', 'path':themeLocation + feat+'/', 'featureType':feat})
        #This is going to now show the progress window...
        count = 0.0
        loaderLength = len(self.featListLoading) + 1.0
        image_loader = ImageLoader(50, 50, '#000000')
        for feature in self.featListLoading:
            count = count + 1
            progressbarLoading.set_text(feature['text'])
            progressbarLoading.set_fraction(count/loaderLength)
            while gtk.events_pending():
                gtk.main_iteration()
            locationsToLoad = [os.path.expanduser(feature['path'])]
            for locationTheme in locationsToLoad:
                for items in (os.listdir(locationTheme)):
                    if os.path.isfile(locationTheme+items) == True:
                        pixbuf = image_loader.load_cached_image(locationTheme+items)
                        scaled_buf = pixbuf.scale_simple(50,50,gtk.gdk.INTERP_BILINEAR)
                        pictureLocation = (locationTheme+items)
                        for feat in self.featureList:
                            if feature['featureType'] == feat:
                                self.featuresMade[feat].model.append([pictureLocation , scaled_buf])
                    else:
                        print("Found a folder, but I'm not loading what's inside.")
        #Hide out the loader
        image_loader.close()
        self.lastThemeLocation = themeLocation
        progressbarLoading.hide()
        comboboxThemePicker.show()

    def removeFeature(self, widget):
        """
        Remove a feature from the self.currentFace objectstack
        """
        self.currentFace.deleteFeature(self.getSelectedPage())
        self.applyChanges()

    def arrowUpClicked(self, widget):
        """
        Apply changes after the up-arrow has been clicked
        """
        #Take a snapshot before
        svgHandle = rsvg.Handle(data = self.currentFace.printMe())
        image1 = svgHandle.get_pixbuf()
        if self.currentFace.raiseFeature(self.getSelectedPage()):
            self.applyChanges()
            return
        svgHandle = rsvg.Handle(data = self.currentFace.printMe())
        image2 = svgHandle.get_pixbuf()
        while (self.notChanged(image1,image2)):
            if self.currentFace.raiseFeature(self.getSelectedPage()):
                break
            svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            image2 = svgHandle.get_pixbuf()
        self.applyChanges()

    def arrowDownClicked(self, widget):
        """
        Apply changes after the down-arrow has been clicked
        """
        #Take a snapshot before
        svgHandle = rsvg.Handle(data = self.currentFace.printMe())
        image1 = svgHandle.get_pixbuf()
        if self.currentFace.lowerFeature(self.getSelectedPage()):
            self.applyChanges()
            return
        svgHandle = rsvg.Handle(data = self.currentFace.printMe())
        image2 = svgHandle.get_pixbuf()
        while (self.notChanged(image1,image2)):
            if self.currentFace.lowerFeature(self.getSelectedPage()):
                break
            svgHandle = rsvg.Handle(data = self.currentFace.printMe())
            image2 = svgHandle.get_pixbuf()
        self.applyChanges()

    def getSelectedPage(self):
        """
        Take the current notbook page and return it back to the caller with the name of the notebook
        """
        notebookFeatures = self.meMakerWin.get_widget("notebookFeatures")
        testing = notebookFeatures.get_current_page()
        return self.featureList[testing].lower()

    def avatarReset(self, widget = None):
        """
        Reset the avatar image to nothing
        """
        self.currentFace.clearMe()
        self.applyChanges()

    def loadAbout(self, widget):
        """Open the about diolog"""
        aboutDialog = gtk.glade.XML( "memaker.glade", "aboutdialogMeMaker")
        frmAbout = aboutDialog.get_widget("aboutdialogMeMaker")
        result = frmAbout.run()
        frmAbout.destroy()
        return result

    def featureClicked(self, iconviewList, modelTheme):
        """
        Figure out what was clicked and then apply that change to 
        the current face object stack and redraw the drawing area.
        """
        indexFeature = iconviewList.get_selected_items()
        try:
            selectedItems = iconviewList.get_selected_items()[0][0]
            locationFeature = modelTheme[selectedItems][0]
        except IndexError:
            return
        self.currentFace.addFeature(locationFeature,self.getSelectedPage())
        iconviewList.unselect_all()
        self.applyChanges()

    def applyChanges(self):
        """
        Redraw the image to make sure it is all shown on the screen.
        """
        self.avatarPicture.queue_draw()

    def draw_scene(self, a, v):
        """Draw the current face Object stack with cairo """
        self.svgHandle = rsvg.Handle(data = self.currentFace.printMe())
        cr = self.avatarPicture.window.cairo_create()
        self.clearIt.render_cairo(cr)
        self.svgHandle.render_cairo(cr)

    def saveLoadFeatures(self, dialog_action, file_type, file_name="MyAvatar", FILE_EXT = {"Scalable Vector Graphics | SVG":"svg","Bitmap Format | BMP":"bmp","Gnome About Me | .face":".face","JPeg Format | JPG":"JPG","Portable Network Graphic | PNG":"PNG"}):
        """
        dialog_action - The open or save mode for the dialog either
        gtk.FILE_CHOOSER_ACTION_OPEN, gtk.FILE_CHOOSER_ACTION_SAVE
            file_name - Default name when doing a save
        """
        if (dialog_action==gtk.FILE_CHOOSER_ACTION_OPEN):
            dialog_buttons = (gtk.STOCK_CANCEL
                                , gtk.RESPONSE_CANCEL
                                , gtk.STOCK_OPEN
                                , gtk.RESPONSE_OK)
        else:
            dialog_buttons = (gtk.STOCK_CANCEL
                                , gtk.RESPONSE_CANCEL
                                , gtk.STOCK_SAVE
                                , gtk.RESPONSE_OK)

        file_dialog = gtk.FileChooserDialog(title="MeMaker Save Avatar"
                    , action=dialog_action
                    , buttons=dialog_buttons)
        if (dialog_action==gtk.FILE_CHOOSER_ACTION_SAVE):
            file_dialog.set_current_name(file_name+"."+file_type)
            for extension in FILE_EXT.keys():
                filters = gtk.FileFilter()
                filters.set_name(extension)
                filters.add_pattern("*." + FILE_EXT[extension])
                file_dialog.add_filter(filters)
        filters = gtk.FileFilter()
        filters.set_name("All files")
        filters.add_pattern("*")
        file_dialog.add_filter(filters)
        #Init the return value
        result = ""
        if file_dialog.run() == gtk.RESPONSE_OK:
            result = file_dialog.get_filename()
        file_dialog.destroy()
        return result
    def loadPickledHead(self):
        """
        Restore the pickled file ~/.MeMaker/pickledHeads to restore all of the heads or use.
        """
        picklePlace = os.path.expanduser("~/.MeMaker/pickledHeads")
        if os.path.isfile(picklePlace):
            fileToPickle = file(picklePlace, "r")
            self.loadedHeads = pickle.load(fileToPickle)
        else:
            print "Previously session not found.  Continuing without loading the last session."
        
    def saveAndQuit(self, widget):
        """
        Pickle all the face objects to a file called pickledHeads and quit memaker.
        """
        picklePlace = os.path.expanduser("~/.MeMaker/pickledHeads")
        fileToUnpickle = file(picklePlace, "w")
        pickle.dump(self.loadedHeads, fileToUnpickle)
        fileSave = file(os.path.expanduser("~/.MeMaker/conf.conf"), "w")
        fileSave.write(self.themeLocation)
        gtk.main_quit()
    def testing(n, action):
            assert action == "help"
            print "You clicked Help"
            n.close()
    def notChanged(self, originalPixbuf, newPixbuf):
        """
        Takes two pixbufs and returns if they are the exact same or different. True/False
        """
        return originalPixbuf.get_pixels() == newPixbuf.get_pixels()
        
app = MeMakerGui()
gtk.main()
