#!/usr/bin/python
# -*- coding: utf-8 -*-
# 	$Id$	

licence={}
licence['en']="""
    file mainWindow.py
    this file is part of the project scolasync
    
    Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>

    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 version3 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.  If not, see <http://www.gnu.org/licenses/>.
"""

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import ownedUsbDisk, help, copyToDialog1, chooseInSticks, usbThread, preferences
import os.path, operator, subprocess, dbus, re, time
from notification import Notification
import db
from globaldef import logFileName

# cette donnée est globale, pour être utilisé depuis n'importe quel objet
globalDiskData=ownedUsbDisk.Available(True,access="firstFat")

def firstdir(l):
    """
    Renvoie le premier répertoire existant d'une liste de propositions
    @param l la liste de propositions
    """
    for d in l:
        if os.path.isdir(d): return d
    return None
   
def _dir(which):
    """
    Renvoie le répertoire où trouver telle ou telle ressource
    @param which le type de ressource
    """
    if which=="lang":
        return firstdir(["/usr/share/scolasync/lang", "lang"])
    elif which=="help":
        return firstdir(["/usr/share/scolasync/help", "help"])
    elif which=="share":
        return firstdir(["/usr/share/scolasync/","share"])
    return None


class mainWindow(QMainWindow):
    def __init__(self, parent, opts, locale="fr_FR"):
        """
        Le constructeur
        @param parent un QWidget
        @param opts une liste d'options extraite à l'aide de getopts
        @param locale la langue de l'application
        """
        QMainWindow.__init__(self)
        QWidget.__init__(self, parent)
        self.locale=locale
        from Ui_mainWindow  import Ui_MainWindow
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.t=self.ui.tableView
        self.opts=opts
        self.applyPreferences()
        self.timer=QTimer()
        self.updateButtons()
        self.timer.start(2000)
        self.threads=[]
        QObject.connect(self.timer, SIGNAL("timeout()"), self.checkDisks)
        QObject.connect(self.ui.helpButton, SIGNAL("clicked()"), self.help)
        QObject.connect(self.ui.umountButton, SIGNAL("clicked()"), self.umount)
        QObject.connect(self.ui.toButton, SIGNAL("clicked()"), self.copyTo)
        QObject.connect(self.ui.fromButton, SIGNAL("clicked()"), self.copyFrom)
        QObject.connect(self.ui.delButton, SIGNAL("clicked()"), self.delFiles)
        QObject.connect(self.ui.preferenceButton, SIGNAL("clicked()"), self.preference)
        QObject.connect(self.ui.tableView, SIGNAL("doubleClicked(const QModelIndex&)"), self.tableClicked)

    def applyPreferences(self):
        """
        Applique les préférences et les options de ligne de commande
        """
        global globalDiskData
        prefs=db.readPrefs()
        self.workdir=prefs["workdir"]
        # on active les cases à cocher si ça a été réclamé par les options
        # ou par les préférences
        self.checkable=("--check","") in self.opts or ("-c","") in self.opts or prefs["checkable"]
        other=ownedUsbDisk.Available(self.checkable,access="firstFat")
        globalDiskData=other
        self.header=ownedUsbDisk.uDisk.headers(self.checkable)
        self.connectTableModel(other)
        self.t.setSortingEnabled(True)
        self.t.resizeColumnsToContents()

    def changeWd(self, newDir):
        """
        change le répertoire par défaut contenant les fichiers de travail
        @param newDir le nouveau nom de répertoire
        """
        self.workdir=newDir
        db.setWd(newDir)

    def tableClicked(self, idx):
        """
        fonction de rappel pour un double clic sur un élément de la table
        @param idx un QModelIndex
        """
        global globalDiskData
        c=idx.column()
        r=idx.row()
        h=self.header[c]
        if c==0 and self.checkable:
            # case à cocher
            pass
        elif c==1:
            # case du propriétaire
            self.editOwner(r)
        elif "device-mount-paths" in h:
            cmd=u"nautilus '%s'" %idx.data().toString ()
            subprocess.call(cmd, shell=True)
        elif "device-size" in h:
            path=globalDiskData[r][self.header.index("1device-mount-paths")]
            cmd =u"df '%s'" %path
            dfOutput=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0].split("\n")[-2]
            m = re.match("(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*", dfOutput).groups()
            print m
            dev,total,used,remain,pcent,path=m
            pcent=int(pcent[:-1])
            from diskFull import mainWindow
            w=mainWindow(self,pcent,title=path, total=total, used=used)
            w.show()
        else:
            QMessageBox.warning(None,
                                QApplication.translate("Dialog","Double-clic non pris en compte",None, QApplication.UnicodeUTF8),
                                QApplication.translate("Dialog","pas d'action pour l'attribut %1",None, QApplication.UnicodeUTF8).arg(h))

    def diskFromTableRow(self,ligne):
        """
        trouve le disque qui correspond à une ligne du tableau
        @param ligne la ligne du tableau qui concerne une clé
        @return le disque correspondant à la ligne de tableau (type uDisk)
        """
        global globalDiskData
        stickid=globalDiskData[ligne][self.header.index("5drive-serial")]
        found=False
        for d in globalDiskData.disks.keys():
            if d.showableProp("drive-serial")==stickid:
                found=True
                break
        if found:
            return d
        else:
            return None
        
    def editOwner(self, ligne):
        """
        Édition du propriétaire d'une clé.
        @param ligne la ligne du tableau qui concerne une clé
        """
        global globalDiskData
        student=globalDiskData[ligne][1]
        ownedUsbDisk.editRecord(self.diskFromTableRow(ligne), student)
        other=ownedUsbDisk.Available(self.checkable,access="firstFat")
        globalDiskData=other
        self.connectTableModel(other)
        
    def updateButtons(self):
        """
        Désactive ou active les flèches selon que l'option correspondante
        est possible ou non. Pour les flèches : ça aurait du sens de préparer
        une opération de copie avant même de brancher des clés, donc on les
        active. Par contre démonter les clés quand elles sont absentes ça
        n'a pas d'utilité.
        """
        global globalDiskData
        if len(globalDiskData)>0:
            self.ui.toButton.setEnabled(True)
            self.ui.fromButton.setEnabled(True)
            self.ui.umountButton.setEnabled(True)
        else:
            self.ui.toButton.setEnabled(False)
            self.ui.fromButton.setEnabled(False)
            self.ui.umountButton.setEnabled(False)

    def preference(self):
        """
        lance le dialogue des préférences
        """
        pref=preferences.preferenceWindow()
        pref.setValues(db.readPrefs())
        pref.show()
        pref.exec_()
        if pref.result()==QDialog.Accepted:
            db.writePrefs(pref.values())
            # on applique les préférences tout de suite sans redémarrer
            self.applyPreferences()
            
    def delFiles(self):
        """
        Lance l'action de supprimer des fichiers ou des répertoires dans les clés USB
        """
        global globalDiskData
        titre1=QApplication.translate("Dialog","Choix de fichiers à supprimer",None, QApplication.UnicodeUTF8)
        titre2=QApplication.translate("Dialog","Choix de fichiers à supprimer (jokers autorisés)",None, QApplication.UnicodeUTF8)
        d=chooseInSticks.chooseDialog(self, titre1, titre2)
        ok = d.exec_()
        if ok:
            pathList=map(lambda x: u"%s" %x, d.pathList())
            reply=QMessageBox.critical(None,
                                       QApplication.translate("Dialog","Vous allez effacer plusieurs baladeurs",None, QApplication.UnicodeUTF8),
                                       QApplication.translate("Dialog","Etes-vous certain de vouloir effacer : "+",".join(pathList),None, QApplication.UnicodeUTF8))
            if reply == QMessageBox.Ok:
                for p in globalDiskData:
                    t=usbThread.threadDeleteInUSB(p,pathList,subdir="Travail", logfile=logFileName)
                    t.setDaemon(True)
                    self.threads.append(t)
                    t.start()
        else:
            msgBox=QMessageBox.warning(None,
                                       QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
                                       QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))

    def copyTo(self):
        """
        Lance l'action de copier vers les clés USB
        """
        global globalDiskData
        d=copyToDialog1.copyToDialog1(parent=self, workdir=self.workdir)
        d.exec_()
        if d.ok==True:
            for p in globalDiskData:
                subdir=self.workdir
                t=usbThread.threadCopyToUSB(p,d.selectedList(),subdir=subdir, logfile=logFileName")
                t.setDaemon(True)
                self.threads.append(t)
                t.start()
        else:
            msgBox=QMessageBox.warning(None,
                                       QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
                                       QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))

    def copyFrom(self):
        """
        Lance l'action de copier depuis les clés USB
        """
        global globalDiskData
        titre1=QApplication.translate("Dialog","Choix de fichiers à copier",None, QApplication.UnicodeUTF8)
        titre2=QApplication.translate("Dialog", "Choix de fichiers à copier depuis les baladeurs", None, QApplication.UnicodeUTF8)
        ok=QApplication.translate("Dialog", "Choix de la destination ...", None, QApplication.UnicodeUTF8)
        d=chooseInSticks.chooseDialog(self, title1=titre1, title2=titre2, ok=ok)
        ok = d.exec_()
        if not ok or len(d.pathList())==0 :
            msgBox=QMessageBox.warning(None,
                                       QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
                                       QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))
            return
        # bon, alors c'est OK pour le choix des fichiers à envoyer
        pathList=map(lambda x: u"%s" %x, d.pathList())
        mp=d.selectedDiskMountPoint()
        initialPath=os.path.expanduser("~")
        destDir = QFileDialog.getExistingDirectory(None,
                                                   QApplication.translate("Dialog","Choisir un répertoire de destination",None, QApplication.UnicodeUTF8),
                                                   initialPath)
        if destDir and len(destDir)>0 :
            dest=u"%s" %destDir
            for p in globalDiskData:
                # on devrait vérifier s'il y a des données à copier
                # et s'il n'y en a pas, ajouter des lignes au journal
                # mais on va laisser faire ça dans le thread
                # inconvénient : ça crée quelquefois des sous-répertoires
                # vides inutiles dans le répertoire de destination.
                t=usbThread.threadCopyFromUSB(p,pathList,subdir=self.workdir,
                                              rootPath=mp,
                                              dest=dest,
                                              logfile=logFileName)
                t.setDaemon(True)
                self.threads.append(t)
                t.start()
            # on ouvre nautilus pour voir le résultat des copies
            if QMessageBox.question(None,
                                    QApplication.translate("Dialog","Voir les copies",None, QApplication.UnicodeUTF8),
                                    QApplication.translate("Dialog","Voulez-vous voir les fichiers copiés ?",None, QApplication.UnicodeUTF8)):
                subprocess.call("nautilus '%s'" %dest,shell=True)
        else:
            msgBox=QMessageBox.warning(None,
                                       QApplication.translate("Dialog","Destination manquante",None, QApplication.UnicodeUTF8),
                                       QApplication.translate("Dialog","Veuillez choisir une destination pour la copie des fichiers",None, QApplication.UnicodeUTF8))

    

    def help(self):
        """
        Affiche le widget d'aide
        """
        w=help.helpWindow(None)
        w.loadBrowsers(_dir("help"),self.locale)
        w.show()
        w.exec_()

    def umount(self):
        """
        Démonte et détache les clés USB affichées
        """
        global globalDiskData
        buttons=QMessageBox.StandardButtons(QMessageBox.Ok+QMessageBox.Cancel)
        button=QMessageBox.critical ( self,
                                      QApplication.translate("Main","Démontage des baladeurs",None, QApplication.UnicodeUTF8),
                                      QApplication.translate("Main","Êtes-vous sûr de vouloir démonter tous les baladeurs cochés de la liste ?",None, QApplication.UnicodeUTF8),
                                      buttons=buttons)
        if button!=QMessageBox.Ok:
            return
        # on parcourt les premières partition FAT
        for p in globalDiskData:
            # on trouve leurs disques parents
            for d in globalDiskData.disks.keys():
                if p in globalDiskData.disks[d] and p.selected:
                    # démontage de toutes les partitions du même disque parent
                    for partition in globalDiskData.disks[d]:
                        devfile=partition.getProp("device-file-by-id")
                        if isinstance(devfile, dbus.Array):
                            devfile=devfile[0]
                        subprocess.call("udisks --unmount %s" %devfile, shell=True)
                    # détachement du disque parent
                    devfile_disk=d.getProp("device-file-by-id")
                    if isinstance(devfile_disk, dbus.Array):
                        devfile_disk=devfile_disk[0]
                    subprocess.call("udisks --detach %s" %devfile_disk, shell=True)
                    break
                

    def connectTableModel(self, data):
        """
        Connecte le modèle de table à la table
        @param data les données de la table
        """
        self.visibleheader=[]
        for h in self.header:
            if h in ownedUsbDisk.uDisk._itemNames:
                self.visibleheader.append(self.tr(ownedUsbDisk.uDisk._itemNames[h]))
            else:
                self.visibleheader.append(h)
        self.tm=usbTableModel(self, self.visibleheader,data,self.checkable)
        self.t.setModel(self.tm)
        if self.checkable:
            self.t.setItemDelegateForColumn(0, CheckBoxDelegate(self))
            self.t.setItemDelegateForColumn(1, UsbDiskDelegate(self))
        else:
            self.t.setItemDelegateForColumn(0, UsbDiskDelegate(self))
        
    def checkDisks(self):
        """
        fonction relancée périodiquement pour vérifier s'il y a un changement
        dans le baladeurs, et signaler dans le tableau les threads en cours.
        Le tableau est complètement régénéré à chaque fois, ce qui n'est pas
        toujours souhaitable.
        """
        global globalDiskData
        other=ownedUsbDisk.Available(self.checkable,access="firstFat")
        globalDiskData=other
        self.connectTableModel(other)
        self.updateButtons()
        self.t.resizeColumnsToContents()



class usbTableModel(QAbstractTableModel):
    """
    Un modèle de table pour des séries de clés USB
    """

    def __init__(self, parent=None, header=[], donnees=None, checkable=False):
        """
        @param parent un QObject
        @param header les en-têtes de colonnes
        @param donnees les données
        @param checkable vrai si la première colonne est composée de boîtes à cocher. Faux par défaut
        """
        QAbstractTableModel.__init__(self,parent)
        self.header=header
        self.donnees=donnees
        self.checkable=checkable
        self.pere=parent

    def rowCount(self, parent):
        """
        @parent un QModelIndex
        """
        return len(self.donnees)
    
    def columnCount(self, parent): 
        """
        @parent un QModelIndex
        """
        return len(self.header) 

    def setData(self, index, value, role):
        if index.column()==0 and self.checkable:
            self.donnees[index.row()].selected=value
            return True
        else:
            return QAbstractTableModel.setData(self, index, role)
        
    def data(self, index, role): 
        if not index.isValid(): 
            return QVariant()
        elif role==Qt.ToolTipRole:
            c=index.column()
            h=self.pere.header[c]
            if c==0 and self.checkable:
                return QApplication.translate("Main","Cocher ou décocher cette case en cliquant.",None, QApplication.UnicodeUTF8)
            elif c==1:
                return QApplication.translate("Main","Propriétaire de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour modifier.",None, QApplication.UnicodeUTF8)
            elif "device-mount-paths" in h:
                return QApplication.translate("Main","Point de montage de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour voir les fichiers.",None, QApplication.UnicodeUTF8)
            elif "device-size" in h:
                return QApplication.translate("Main","Capacité de la clé USB ou du baladeur en kO ;<br><b>Double-clic</b> pour voir la place occupée.",None, QApplication.UnicodeUTF8)
            elif "drive-vendor" in h:
                return QApplication.translate("Main","Fabricant de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
            elif "drive-model" in h:
                return QApplication.translate("Main","Modèle de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
            elif "drive-serial" in h:
                return QApplication.translate("Main","Numéro de série de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
            else:
                return ""
        elif role != Qt.DisplayRole: 
            return QVariant()
        if index.row()<len(self.donnees):
            return QVariant(self.donnees[index.row()][index.column()])
        else:
            return QVariant()

    def headerData(self, section, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return QVariant(self.header[section])
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            return QVariant(section+1)
        return QVariant()

    def sort(self, Ncol, order=Qt.DescendingOrder):
        """Sort table by given column number.
        @param Ncol numéro de la colonne de tri
        @param order l'odre de tri, Qt.DescendingOrder par défaut
        """
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.donnees = sorted(self.donnees, key=operator.itemgetter(Ncol))        
        if order == Qt.DescendingOrder:
            self.donnees.reverse()
        self.emit(SIGNAL("layoutChanged()"))

def CheckBoxRect(view_item_style_options):
  check_box_style_option=QStyleOptionButton()
  check_box_rect = QApplication.style().subElementRect(QStyle.SE_CheckBoxIndicator,check_box_style_option)
  check_box_point=QPoint(view_item_style_options.rect.x() + view_item_style_options.rect.width() / 2 - check_box_rect.width() / 2, view_item_style_options.rect.y() + view_item_style_options.rect.height() / 2 - check_box_rect.height() / 2)
  return QRect(check_box_point, check_box_rect.size())

class CheckBoxDelegate(QStyledItemDelegate):
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self,parent)

    def paint(self, painter, option, index):
        checked = index.model().data(index, Qt.DisplayRole).toBool()
        check_box_style_option=QStyleOptionButton()
        check_box_style_option.state |= QStyle.State_Enabled
        if checked:
            check_box_style_option.state |= QStyle.State_On
        else:
            check_box_style_option.state |= QStyle.State_Off
        check_box_style_option.rect = CheckBoxRect(option);
        QApplication.style().drawControl(QStyle.CE_CheckBox, check_box_style_option, painter)

    def editorEvent(self, event, model, option, index):
        if ((event.type() == QEvent.MouseButtonRelease) or (event.type() == QEvent.MouseButtonDblClick)):
            if (event.button() != Qt.LeftButton or not CheckBoxRect(option).contains(event.pos())):
                return False
            if (event.type() == QEvent.MouseButtonDblClick):
                return True
        elif (event.type() == QEvent.KeyPress):
            if event.key() != Qt.Key_Space and event.key() != Qt.Key_Select:
                return False
        else:
            return False
        checked = index.model().data(index, Qt.DisplayRole).toBool()
        result = model.setData(index, not checked, Qt.EditRole)
        return result

        
class UsbDiskDelegate(QStyledItemDelegate):
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self,parent)
        self.okPixmap=QPixmap("/usr/share/icons/Tango/16x16/status/weather-clear.png")
        self.busyPixmap=QPixmap("/usr/share/icons/Tango/16x16/actions/view-refresh.png")

    def paint(self, painter, option, index):
        global globalDiskData
        text = index.model().data(index, Qt.DisplayRole).toString()
        rect0=QRect(option.rect)
        rect1=QRect(option.rect)
        h=rect0.height()
        w=rect0.width()
        rect0.setSize(QSize(h,h))
        rect1.translate(h,0)
        rect1.setSize(QSize(w-h,h))
        QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette, True, text)
        QApplication.style().drawItemText (painter, rect0, Qt.AlignCenter, option.palette, True, QString("O"))
        if usbThread.globalThreads.busy(u"%s" %text):
            QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.busyPixmap)
        else:
            QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.okPixmap)
        
