tempo

 

Une application de gestion du temps par Miko

Multi utilisateur, en production à mon boulot, tourne actuellement avec une quarantaine d'utilisateurs simultanés.

Copie d'écran:

Le choix d'une activité lance un compteur qui sera enregistré à l'arrêt ou au changement d'activité. Pour les activités, il suffit de renseigner un fichier "activites.txt", qui sera placé dans le répertoire du script.

Le programme s'adapte donc à tout type d'activités, un menu appelé avec le clic droit permet également à l'utilisateur de consulter le temps passé par semaine, mois ou année, ainsi que le classement décroissant des activités.

Si vous voulez l'utiliser sous Linux, il faut juste adapter le code de recherche du fichier de conf à votre environnement.

Et si jamais vous l'utilisez chez vous, dites-le moi, ça me fera plaisir...

le fichier de config contient juste ça:

  #police utilisée pour l'affichage
  set ::police {Arial  12}
  #adresse ip du serveur PostgreSQL
  set ::srvpgSQL localhost

Il vous faut:

Un serveur PostgreSQL sous l'O.S. de votre choix. vous devrez créer une base "tempo", contenant une table "suivi"

   CREATE TABLE suivi   (
  "Ksuivi" integer NOT NULL DEFAULT nextval('"suivi_Ksuivi_seq"'::regclass),
  agt character varying(8) NOT NULL,
  semaine character(2),
  activite character varying(50),
  secondes integer,
  jour integer,
  mois integer,
  annee integer,
  CONSTRAINT pk_suivi PRIMARY KEY ("Ksuivi")
   )
   WITHOUT OIDS;
   ALTER TABLE suivi OWNER TO postgres;
   COMMENT ON TABLE suivi IS 'suivi de l''activité d''un utilisateur';

Au boulot, j'utilise également une table de correspondance entre les utilisateurs et les services dans lesquels ils travaillent, ce qui permet d'avoir des statistiques par service. Contactez-moi (michel.salvagniac at free.fr) si ces détails vous interessent.

Le code:

   # gestion du temps de travail
   #
   #
   # M.S janvier 2007
   # correction présence d'astropof dans les activités 17/05/07
   # consultation du temps effectué 18/05/07

   #connexion à la base. retourne le handle si succès
   proc connx {} {
    if [catch {set conn [pg_connect -conninfo "hostaddr=$::srvpgSQL dbname=tempo user=postgres password=Mystere"]}]  {
   tk_messageBox -message "Impossible de se connecter à la base" -icon error -title FATAL
        exit
         } else  {
            return $conn
        }
   }
   #deconnexion
   proc deconnx {} {
     pg_disconnect $::hconn
   }

   #ecrit les donnees , retourne un handle à passer à pg_result
   proc ecritdonnees {conn cmd} {
       set result [pg_exec $conn $cmd]
        return [pg_result $result -status]

   }

   proc initialise {} {
   package require BWidget
   package require pgintcl
   package require Tablelist
   set ::applidir [file dirname [info script]]
   set ::phrase "" ;# chaine mémorisée puis écrite
   set ::compteur 0 ;# compteur
   set ::applidir [file dirname [info nameofexecutable]]
   set ::application Tempo
        if [catch {source  gest_serv.conf}] {
            tk_messageBox -type ok -icon info -message "Fichier de conf non trouvé.."
            set ::police {Arial  12}
            set ::srvpgSQL 1.2.3.4
        }
   set ::hconn [connx]
   set ::semaine [clock format [clock seconds] -format %W ]
   set ::jour [string trimleft [clock format [clock seconds] -format %d ] 0]
   set ::mois [string trimleft [clock format [clock seconds] -format %m ] 0]
   set ::annee [clock format [clock seconds] -format %Y]
   #lecture de l'user courant
   if { $::tcl_platform(os)=="Windows NT" } {
    set ::username [string toupper $::env(USERNAME)]
   } elseif { $::tcl_platform(os)=="Linux" } {
    set ::username [string toupper $::env(USER)]
   } else {
    tk_messageBox -type ok -icon info -message "Plateforme non supportée"
    exit
   }

   set ::repsav [file join [file dirname ~]  $::env(USERNAME) Personnel]
      if ![file exists $::repsav] {
        #et ici, en local
        set ::repsav [file join [file dirname ~]  $::env(USERNAME) "Mes Documents"]
      }

   font create Label  -family Arial -size 12
   option add *Label.font Label widgetDefault
   #images pour les boutons Valider et Annuler
   image create photo actcheck16 -data {
   R0lGODlhEAAQAIIAAPwCBMT+xATCBASCBARCBAQCBEQCBAAAACH5BAEAAAAA
   LAAAAAAQABAAAAM2CLrc/itAF8RkdVyVye4FpzUgJwijORCGUhDDOZbLG6Nd
   2xjwibIQ2y80sRGIl4IBuWk6Af4EACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYg
   UHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJp
   Z2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=
   }
   image create photo actcross16 -data {
   R0lGODlhEAAQAIIAAASC/PwCBMQCBEQCBIQCBAAAAAAAAAAAACH5BAEAAAAA
   LAAAAAAQABAAAAMuCLrc/hCGFyYLQjQsquLDQ2ScEEJjZkYfyQKlJa2j7AQn
   MM7NfucLze1FLD78CQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJz
   aW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVz
   ZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7
   }

   #icône racine
   image create photo folder_cyan-16 -data {
   R0lGODlhEAAQAIYAAPwCBAQCBExKTBwWHNTKzOzq7ExGTCwqLERCRBSKjBRS
   VGxubKymrNTS1Hx2fCQiJMzq7NTy7IzKxHR2dFTW1Ey2rITKzNzy9JTSzByS
   lHRydKSipDTW1ByelGzCvIzOzByOjFRSVCy2rCSalGS+tJzSzBxudCR6hCSC
   jITGxMTm5Pz+/JSutDyChBxydCyOlITKxPz6/PTy9Nza3ISqrGSyrOzm7MzK
   zDQyNMzGzNze3OTi5MTCxNTO1Ly6vLSutGRiZKyurHRudDw6PAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAA
   LAAAAAAQABAAAAe2gACCAAECg4eIAAMEBQYGB4mDCAkKCwwNjYUOAYcPEBES
   lBMMjAUFnIIPFBUWEBcYGQoaG5gGggMcHRUeFh+fGCCVIakiICMduiQWJa4f
   CKkdJicoCcjJKSrPAAIrBSwtLtMv1jDaCDEyMgUzNC3SKCA12gYFMjY2KzMz
   N98uLdo4cujYoa9BAx4IeSywJSgAjgU8evTIgdDHj4WJHAL54cPiDyAMIzkM
   sSGIkCGREAU4gAOAn0AAIf5oQ3JlYXRlZCBieSBCTVBUb0dJRiBQcm8gdmVy
   c2lvbiAyLjUNCqkgRGV2ZWxDb3IgMTk5NywxOTk4LiBBbGwgcmlnaHRzIHJl
   c2VydmVkLg0KaHR0cDovL3d3dy5kZXZlbGNvci5jb20AOw==
   }

   #icône racine des activités
   image create photo folder_cyan_open-16 -data {
   R0lGODlhEAAQAIYAAPwCBAQCBExKTBQWFNze3Ozq7ERCRCwqLPz+/PT29Ozu
   7OTm5FRSVHRydIR+fIyCdMzGxAxOTBSGhIx6XJSKfMzKzERGRAxOVKTq5Mzy
   7KTW1Ozi1OzizEzSxGTWzHzCvNTq5OTSvNTCnIRyVNTS1BzKvCSalGy6tAQ6
   XMyyjMzOzDw+PByqtCR6fCyGjHRmTMS+vJSytDyChCyWlGReVOTi5AxGRKyu
   rDQ2NNza3NTW1AxKTJyenGxqbMTCxMTGxLy6vLS2tLSurKyqrCwuLFxaXKSm
   pDw6PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAA
   LAAAAAAQABAAAAfEgACCAAECg4eIAAMEBQYCB4mHAQgJCgsLDAEGDQGIkw4P
   BQkJBZcQnRESEREIDwoPExSipBUWFxgZGhIXFwkbHBQToQQGFx0eHxoguhEX
   HCEiIyQGABclJiYnycsSKCkjKisAESwtLi7Y2soXLzDUAaMxMuYuM9kXNDDi
   AAYFBTU15NGTYeMGNQA4ahAgkEMHiQoxZrTYwWMfIRw9fKio8MMHECAReByU
   hIPGDSBBhAyJYDFRACJFeBi5cCSSpwM4APgJBAAh/mhDcmVhdGVkIGJ5IEJN
   UFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTgu
   IEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNv
   bQA7
   }

   #icône activités
   image create photo txt-16 -data {
   R0lGODlhEAAQAIYAAPwCBFxaVPzKjMR6RNze3Ly2tJyanPyODPz+/Ozq7Gxq
   bPyKBPyGDPzGjPT29HRydMzOzDQuJOx6BPyGBPyKDOTWzIyKjERGRDQyNKRO
   BOR2BOzezIyKhBwOBKRKBNSOXOza1CQOBPyCDOySZPzizPTWxPTy7Pz69Pz2
   9PTq3LyqlMxmJNRyROy2lPTu5OTi3MSulFxGNIRWLMR+XPTKrOzStPTm3IRS
   NOzizOzavLymhLSyrEw6LKRuVIx6ZNzSvOzexNzWxNzW1FROTBwaFKSShOzW
   vOTOpOTazJyWjHRqXNzKrOTStLyifMS+vMS+rMS6pLymfLyedAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAA
   LAAAAAAQABAAAAfOgACCAAGFhoaDAgOCAQSOjgUBBgGCB4qEBAiaCAkKCJMA
   CwwNA42bCA4PEA4GERITFKQVpwgWDwoXGBkaE6MDG6cOBAUcGB0eGgsTDR8g
   mw4OGwURACHIIiMkJSYIJygpKSoYgtYrLC0kLdAuKS8w1IIdMTIzNDUbLjYp
   G+KDABcRbtRrYWMDjhw6xg0KsCMAjx4+fmwAEiShvwBCChgYQqSIkRpHdMBj
   ZCIFkh1JlCxhcqSJQkZOnjyBAgOGjihSpLwkhAFDBJ9Ae0bwEwgAIf5oQ3Jl
   YXRlZCBieSBCTVBUb0dJRiBQcm8gdmVyc2lvbiAyLjUNCqkgRGV2ZWxDb3Ig
   MTk5NywxOTk4LiBBbGwgcmlnaHRzIHJlc2VydmVkLg0KaHR0cDovL3d3dy5k
   ZXZlbGNvci5jb20AOw==
   }

   option add *Tablelist.setFocus		yes
   option add *Tablelist.setGrid		yes
   #options permettant le tri... ascii par défaut!!!
   option add *Tablelist.labelCommand	tablelist::sortByColumn
   option add *Tablelist.labelCommand2	tablelist::addToSortColumns
   }

  #Centrage des fenêtres filles sur la mère
  proc Centrer { fenetre } {
  set x0 [ expr ( [winfo width .] - [winfo reqwidth $fenetre ] ) / 2 ]
  set y0 [ expr ( [winfo height .] - [winfo reqheight $fenetre ] ) / 2 ]
  wm geometry $fenetre +[ expr [winfo x .] + $x0 ]+[ expr [winfo y .] + $y0 ]

  }

   # lecture du fichier des activités
   # en renvoie la liste tcl
   proc ListeActivites {} {
   set lefichier activites.txt
   set contenu [read -nonewline [open $lefichier r]]
   return [split $contenu \n]
   }

   #renvoie la plus longue chaine d'une liste
   proc PlusLongueChaine {laliste} {
  set longueur 0
  foreach element $laliste {
    if {[string length $element] > $longueur } {
      set longueur [string length $element]
    }
  }
  return $longueur
   }

   proc Gui {} {
  set listeactivite [ListeActivites]
  set nbcar [PlusLongueChaine $listeactivite]
  set nbactivites [llength $listeactivite]
  set largeur  [expr $nbcar *5]
  set hauteur [expr $nbactivites * 20]
  wm title . Tempo
  wm resizable . false false
  wm protocol . WM_DELETE_WINDOW {Quitter}
  wm geometry . +2+2
  frame .princip -width $largeur -height $hauteur
  Tree .princip.arb  -width [expr $nbcar + 5] -height [expr $nbactivites + 3 ]
  .princip.arb insert  end root arret -text ARRET -font $::police -open 1 \
      -image folder_cyan-16
      .princip.arb insert end root activites -text ACTIVITES -font $::police -open 1 \
      -image folder_cyan_open-16

  foreach  element $listeactivite {

    .princip.arb insert end activites [ string map {" " _ } $element] -text  $element -open 1 \
        -image txt-16 -font $::police

  }

  menu .menubar -tearoff 0
  .menubar add command -label Quitter -command Quitter
    #.menubar add command -label "Préférences" -command {}
    .menubar add separator
    .menubar add cascade -label "En cours" -menu .menubar.r
    menu .menubar.r -tearoff 0
  .menubar.r  add command -label "Annuel" -command [list Consulte annee 1]
  .menubar.r  add command -label "Classement annuel" -command [list Consulte annee 0]
  .menubar.r  add separator
  .menubar.r  add command -label "Mensuel" -command [list Consulte mois  1]
  .menubar.r  add command -label "Classement mensuel" -command [list Consulte mois 0]
  .menubar.r  add separator
  .menubar.r  add command -label "Hebdomadaire" -command [list Consulte semaine 1]
  .menubar.r  add command -label "Classement  hebdo" -command [list Consulte semaine 0]
   frame .infos
  label .infos.texte -text "" -anchor w
  frame .info2
  label .info2.compte -textvariable ::compteur -anchor w
  pack .princip .princip.arb -side top
  pack .infos -side left
  pack .infos.texte
  pack .info2 -side right
  pack .info2.compte
  bind .princip.arb <<TreeSelect>> {NodeClick}
  bind . <Button-3> {tk_popup .menubar %X %Y}
  # on démarre en pause:
  .princip.arb selection set arret
  set ::noeudcourant arret
  .infos.texte configure -text $::noeudcourant -foreground red
  # trace variable after_id w [incr compteur ]
   focus -force .
  }

   #----------------------------------------------------------------------------
   proc Quitter {} {
   #tester si le compteur tourne...
   if $::compteur {
  tk_messageBox -message "Vous devez valider avant de quitter" -title Opopop!!! -icon warning -parent .princip
  return
 }
  #on ferme la base
  if [info exists ::hconn] {
    deconnx
    #tk_messageBox -message "base fermée" -title bye...
  }
  destroy .
  exit
  }

  proc NodeClick { } {
	global   after_id
  #mémorisation du noeud cliqué
  set node [.princip.arb selection get ]
  if [string equal $node activites] {
        if [string equal $::noeudcourant "" ] {
          set ::noeudcourant arret
        }
        .princip.arb selection set [ string map {" " _ } $::noeudcourant]
    return
  }
  if {[string equal $node arret] & !$::compteur} {
        return
  }
  #tk_messageBox -message "valeur de node: $node"
  #return
  if {[ tk_dialog  .w Choix Valider? {} 0 Ok Annuler ] == 0 } {
	  # on valide le changement d'activité
    if ![string equal $node arret] {
      #on a démarré?
	    if  $::compteur  {
      #on lit l'activité prédemment mémorisée
      set activite [ string map {_ " " ' ''} $::noeudcourant]
      #on écrit dans la base
      set cmd_sql   "BEGIN TRANSACTION;\
        INSERT INTO suivi ( agt, semaine, activite, secondes, jour, mois, annee  ) VALUES\
        ('$::username' , '$::semaine' , '$activite' , $::compteur, $::jour ,$::mois , $::annee);\
        COMMIT;"
        if [string equal [ecritdonnees $::hconn $cmd_sql] PGRES_FATAL_ERROR] {
          tk_messageBox -message "Erreur d'écriture dans la table suivi" -icon error -title Erreur!!!
          return
        }

      #on remet le compteur à zéro
      set ::compteur 0
      #on mémorise la nouvelle activité (son noeud)
      set ::noeudcourant   $node
      #on affiche l'activité
      .infos.texte configure -text [.princip.arb itemcget $::noeudcourant -text] -foreground green
      .info2.compte configure -foreground green
      return
      } else {
        TimerFunction start
        set ::noeudcourant $node
        #on affiche l'activité
        .infos.texte configure -text [.princip.arb itemcget $node -text] -foreground green
        .info2.compte configure -foreground green
        return
      }
    } else  {
      if $::compteur {
    set activite [ string map {_ " " ' ''} $::noeudcourant]

        set cmd_sql   "BEGIN TRANSACTION;\
            INSERT INTO suivi ( agt, semaine, activite, secondes, jour, mois, annee  ) VALUES\
            ('$::username' , '$::semaine' , '$activite' , $::compteur, $::jour ,$::mois , $::annee);\
            COMMIT;"
          if [string equal [ecritdonnees $::hconn $cmd_sql] PGRES_FATAL_ERROR] {
          tk_messageBox -message "Erreur d'écriture dans la table suivi" -icon error -title Erreur!!!
          return
        }

        #	 tk_messageBox -type ok -icon info -message $phrase
        TimerFunction stop
        set ::compteur 0
        set ::noeudcourant $node
        .infos.texte configure -text [.princip.arb itemcget $node -text] -foreground red
        .info2.compte configure -foreground red
        return
      } else  {
        TimerFunction stop
        set ::compteur 0
        return
      }
    }

	} else {
  #on annule le changement d'activité
    if [string equal $::noeudcourant "" ] {
      set ::noeudcourant arret
    }
    .princip.arb selection set [ string map {" " _ } $::noeudcourant]
	}

  }
   #proc tustée sur le wiki anglophone
   proc TimerFunction {state {rate {1000}}} {
   global after_id
    if { $state == "start" } {

        set after_id [after $rate TimerFunction start $rate]
		incr ::compteur
    } elseif { $state == "stop" } {
        after cancel $after_id
    }
 }

   proc Consulte {option1 option2} {

  switch -exact $option1 {
      annee {
        if $option2 {
          set cmd_sql   "BEGIN TRANSACTION;\
              SELECT agt, activite, secondes, jour, semaine, mois, annee FROM suivi \
              WHERE agt LIKE '$::username' AND annee=$::annee;\
              COMMIT;"
        } else  {
          set cmd_sql   "BEGIN TRANSACTION;\
              select activite, sum(secondes) as total  from \
              (select  activite, secondes ,jour, mois, annee from suivi\
              WHERE agt LIKE '$::username' AND annee=$::annee )AS ssuivi\
              group by activite\
              order by total desc;\
                        COMMIT;"
        }

      }
      mois {
        if  $option2 {
          set cmd_sql   "BEGIN TRANSACTION;\
              SELECT agt, activite, secondes, jour, semaine, mois, annee FROM suivi\
              WHERE agt LIKE '$::username' AND mois=$::mois AND annee=$::annee;\
              COMMIT;"
        } else  {
          set cmd_sql   "BEGIN TRANSACTION;\
              select activite, sum(secondes) as total  from \
              (select  activite, secondes ,jour, mois, annee from suivi\
              WHERE agt LIKE '$::username' AND mois=$::mois AND annee=$::annee)AS ssuivi\
              group by activite\
              order by total desc;\
              COMMIT;"
        }

      }
      semaine {
        if  $option2 {
          set cmd_sql   "BEGIN TRANSACTION;\
              SELECT agt, activite, secondes, jour, semaine, mois, annee FROM suivi
              WHERE agt LIKE '$::username' AND semaine=$::semaine AND annee=$::annee;\
              COMMIT;"
        } else  {
          set cmd_sql   "BEGIN TRANSACTION;\
              select activite, sum(secondes) as total  from \
              (select  activite, secondes ,jour, mois, annee from suivi\
              WHERE agt LIKE '$::username' AND semaine=$::semaine AND annee=$::annee)AS ssuivi\
              group by activite\
              order by total desc;\
              COMMIT;"
        }
      }
  }

    ExecSQL $cmd_sql consulte
   }

   #affiche la sortie de la commande Sql dans une tablelist
   #expediteur est la proc appelante(pour debug)
   proc ExecSQL {sql expediteur} {
    . configure -cursor wait
      update
  destroy .pw
  toplevel .pw
  wm title .pw Données

   wm resizable .pw 1 1
   frame .pw.ftext  -relief groove -bd 2 -width 640 -height 480
    #on récupère d'abord les noms de colonnes
    #mettre ici un catch pour gérer les erreurs SQL
    set result [pg_exec $::hconn $sql]
    set colonnes [pg_result $result -attributes]
    set lecsv [list2csv $colonnes]\n
    if {[string equal $colonnes PGRES_FATAL_ERROR]} {
        tk_messageBox -message "Erreur dans proc ExecSQL($expediteur)" -icon error -title Erreur!!!
        destroy .pw
        . configure -cursor arrow
        return
    }
    if {[llength $colonnes]==0} {
        #pas de valeurs retournées
         destroy .pw
          . configure -cursor arrow
        tk_messageBox -message "Pas de données" -icon info -title OK

        return
    }

    foreach nom_col $colonnes {
        lappend listecolonnes  0 [string toupper $nom_col] center
    }

    scrollbar .pw.ftext.sy -orient vertical -command ".pw.ftext.tl yview"
    scrollbar .pw.ftext.sx -orient horizontal -command ".pw.ftext.tl xview"

    tablelist::tablelist .pw.ftext.tl -columns $listecolonnes  -font {Arial 12 } -labelfont {Arial 12 bold} -width 80\
     -stripebackground #CDFFD8  -yscrollcommand [list .pw.ftext.sy set]  -xscrollcommand [list .pw.ftext.sx set]

    #on lit maintenant le reste de la base
    #on récupère d'abord les numéros de lignes
    set nbchamps [llength $colonnes]
    set messageSql [pg_result $result -llist]

    #calcul du nombre de lignes
    set nblignes [llength $messageSql]

    for {set i 0 } {$i < $nblignes } {incr i } {
        .pw.ftext.tl insert end [lindex $messageSql $i]
        append lecsv [list2csv [lindex $messageSql $i]]\n
    }
      menu .menuexp -tearoff 0
      .menuexp add command -label Export -command [list Export $lecsv]
      .menuexp add separator
     .menuexp add command -label Fermer -command [list destroy .pw]
    pack .pw.ftext -expand 1 -fill both -padx 2 -pady 2
    #on gagne à être explicite avec grid...
    grid .pw.ftext.tl -column 0 -row 0 -sticky nsew
    grid .pw.ftext.sx -column 0 -row 1 -sticky ews
    grid .pw.ftext.sy  -column 1 -row 0 -sticky nsw
    #sans ces deux lignes, la tablelist ne colle pas à sa frame
    grid rowconfigure .pw.ftext 0 -weight 1
    grid columnconfigure .pw.ftext 0 -weight 1
    . configure -cursor arrow
    .pw.ftext configure -cursor arrow
    .pw.ftext.tl configure -cursor arrow
  bind .pw <Button-3> {tk_popup .menuexp %X %Y}

   }

   proc Export {contenu} {
    set typesfich {
      {{Fichiers Csv} {.csv} }
    }
    set choix [ tk_getSaveFile -title "$::application : Sauvegarder" -defaultextension .csv -filetypes $typesfich\
        -initialdir ::repsav -initialfile $::semaine$::mois$::annee.csv]
    if [string equal $choix ""] {
      return
    }
  if [ catch { open $choix { CREAT WRONLY TRUNC } } FIC ] {
    tk_messageBox -type ok -icon warning \
        -message "Impossible d'ouvrir le fichier $choix en écriture"
    return
  } else {
    puts -nonewline $FIC $contenu
    close $FIC
  }

   }

  proc list2csv {values {sepChar ,}} {
  #transforme une liste en csv (extrait de tcllib)
  set out ""
  set sep {}
  foreach val $values {
    if {[string match "*\[\"$sepChar\]*" $val]} {
      append out $sep\"[string map [list \" \"\"] $val]\"
    } else {
      append out $sep$val
    }
    set sep $sepChar
  }
  return $out
   }

   initialise
   Gui