ScolaSync  5.1
mainWindow.py
Aller à la documentation de ce fichier.
1 #!/usr/bin/python
2 # $Id: mainWindow.py 47 2011-06-13 10:20:14Z georgesk $
3 
4 licence={}
5 licence['en']="""
6  file mainWindow.py
7  this file is part of the project scolasync
8 
9  Copyright (C) 2010-2014 Georges Khaznadar <georgesk@ofset.org>
10 
11  This program is free software: you can redistribute it and/or modify
12  it under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version3 of the License, or
14  (at your option) any later version.
15 
16  This program is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  GNU General Public License for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program. If not, see <http://www.gnu.org/licenses/>.
23 """
24 
25 
26 from PyQt5.QtGui import *
27 from PyQt5.QtCore import *
28 from PyQt5.QtWidgets import *
29 import ownedUsbDisk, help, copyToDialog1, chooseInSticks, usbThread
30 import diskFull, preferences, checkBoxDialog
31 import os.path, operator, subprocess, dbus, re, time, copy
32 from notification import Notification
33 from usbDisk2 import safePath
34 import db
35 import choixEleves
36 import nameAdrive
37 from globaldef import logFileName, _dir
38 
39 # cette donnée est globale, pour être utilisé depuis n'importe quel objet
40 qApp.available=ownedUsbDisk.Available(access="firstFat")
41 
42 activeThreads={} # donnée globale : les threads actifs
43 # cette donnée est mise à jour par des signaux émis au niveau des threads
44 # et elle est utilisée par la routine de traçage des cases du tableau
45 pastCommands={} # donnée globale : les commandes réalisées dans le passé
46 lastCommand=None # donnée globale : la toute dernière commande
47 
48 
52 
53 def registerCmd(cmd,partition):
54  global pastCommands, lastCommand
55  if cmd in pastCommands:
56  pastCommands[cmd].append(partition.owner)
57  else:
58  pastCommands[cmd]=[partition.owner]
59  lastCommand=cmd
60 
61 
63 
65 
66  checkAllSignal=pyqtSignal()
67  checkToggleSignal=pyqtSignal()
68  checkNoneSignal=pyqtSignal()
69  shouldNameDrive=pyqtSignal()
70  pushCmdSignal=pyqtSignal(str, str)
71  popCmdSignal=pyqtSignal(str, str)
72 
73 
77 
78  def __init__(self, parent, locale="fr_FR"):
79  QMainWindow.__init__(self)
80  QWidget.__init__(self, parent)
81  self.locale=locale
82  from Ui_mainWindow import Ui_MainWindow
83  self.ui = Ui_MainWindow()
84  self.ui.setupUi(self)
85  QIcon.setThemeName("Tango")
86  icon=self.setThemedIcon(self.ui.fromButton,"back")
87  self.copyfromIcon=icon
88  self.movefromIcon=QIcon.fromTheme("movefrom",QIcon("/usr/share/scolasync/images/movefrom.png"))
89  self.setThemedIcon(self.ui.toButton,"forward")
90  self.setThemedIcon(self.ui.delButton,"edit-clear")
91  self.setThemedIcon(self.ui.umountButton,"top")
92  self.setThemedIcon(self.ui.redoButton,"go-jump")
93  self.setThemedIcon(self.ui.namesButton,"gtk-find")
94  self.setThemedIcon(self.ui.forceCheckButton,"multimedia-player")
95  self.setThemedIcon(self.ui.preferenceButton,"package_settings")
96  self.setThemedIcon(self.ui.helpButton,"info")
97  # crée le dialogue des nouveaux noms
98  self.namesFullIcon=QIcon.fromTheme("gtk-find-and-replace.svg")
99  self.namesEmptyIcon=QIcon.fromTheme("gtk-find")
100  self.namesFullTip=QApplication.translate("MainWindow", "<br />Des noms sont disponibles pour renommer les prochains baladeurs que vous brancherez", None)
101  self.namesEmptyTip=QApplication.translate("MainWindow", "<br />Cliquez sur ce bouton pour préparer une liste de noms afin de renommer les prochains baladeurs que vous brancherez", None)
103  self.recentConnect="" # chemin dbus pour un baladeur récemment connecté
104  # initialise deux icônes
105  self.initRedoStuff()
106  # initialise le tableau
107  self.t=self.ui.tableView
108  self.proxy=QSortFilterProxyModel()
109  self.proxy.setSourceModel(self.t.model())
110  self.applyPreferences()
111  self.updateButtons()
112  self.setAvailableNames(False)
113  self.operations=[] # liste des opérations précédemment "réussies"
114  self.oldThreads=set() # threads lancés éventuellement encore vivants
115  self.ui.helpButton.clicked.connect(self.help)
116  self.ui.umountButton.clicked.connect(self.umount)
117  self.ui.toButton.clicked.connect(self.copyTo)
118  self.ui.fromButton.clicked.connect(self.copyFrom)
119  self.ui.delButton.clicked.connect(self.delFiles)
120  self.ui.redoButton.clicked.connect(self.redoCmd)
121  self.ui.namesButton.clicked.connect(self.namesCmd)
122  self.ui.preferenceButton.clicked.connect(self.preference)
123  self.ui.tableView.doubleClicked.connect(self.tableClicked)
124  self.checkAllSignal.connect(self.checkAll)
125  self.checkToggleSignal.connect(self.checkToggle)
126  self.checkNoneSignal.connect(self.checkNone)
127  self.shouldNameDrive.connect(self.namingADrive)
128 
129  qApp.available.addHook('object-added', self.cbAdded())
130  qApp.available.addHook('object-removed', self.cbRemoved())
131  self.pushCmdSignal.connect(self.pushCmd)
132  self.popCmdSignal.connect(self.popCmd)
133  return
134 
135 
142 
143  def setThemedIcon(self, button, name, default=None):
144  icon=QIcon()
145  try:
146  icon.addPixmap(QIcon.fromTheme(name).pixmap(32))
147  except:
148  icon.addPixmap("images/icons32/"+name+".png")
149  button.setIcon(icon)
150  return button.icon()
151 
152 
156 
157  def pushCmd(self,owner,cmd):
158  global activeThreads, pastCommands, lastCommand
159  if owner in activeThreads:
160  activeThreads[owner].append(cmd)
161  else:
162  activeThreads[owner]=[cmd]
163  self.tm.updateOwnerColumn()
164  self.updateButtons()
165 
166 
170 
171  def popCmd(self,owner, cmd):
172  global activeThreads, pastCommands, lastCommand
173  if owner in activeThreads:
174  cmd0=activeThreads[owner].pop()
175  if cmd0 in cmd:
176  msg=cmd.replace(cmd0,"")+"\n"
177  logFile=open(os.path.expanduser(logFileName),"a")
178  logFile.write(msg)
179  logFile.close()
180  else:
181  raise Exception(("mismatched commands\n%s\n%s" %(cmd,cmd0)))
182  if len(activeThreads[owner])==0:
183  activeThreads.pop(owner)
184  else:
185  raise Exception("End of command without a begin.")
186  self.tm.updateOwnerColumn()
187  if len(activeThreads)==0 :
188  self.updateButtons()
189 
190  # @param boolfunc une fonction pour décider du futur état de la coche
191  # étant donné l'état antérieur
192  # Modifie les coches des baladeurs
193  #
194 
195  def checkModify(self, boolFunc):
196  model=self.tm
197  index0=model.createIndex(0,0)
198  index1=model.createIndex(len(model.donnees)-1,0)
199  srange=QItemSelectionRange(index0,index1)
200  for i in srange.indexes():
201  checked=bool(i.model().data(i,Qt.DisplayRole))
202  model.setData(i, boolFunc(checked),Qt.EditRole)
203 
204 
206 
207  def checkAll(self):
208  self.checkModify(lambda x: True)
209 
210 
212 
213  def checkToggle(self):
214  self.checkModify(lambda x: not x)
215 
216 
218 
219  def checkNone(self):
220  self.checkModify(lambda x: False)
221 
222  # Gère un dialogue pour renommer un baladeur désigné par
223  # self.recentConnect
224  #
225 
226  def namingADrive(self):
227  if self.availableNames:
228  if self.recentConnect not in qApp.available.targets:
229  return
230  disk=qApp.available.targets[self.recentConnect]
231  hint=db.readStudent(disk.serial, disk.uuid, ownedUsbDisk.tattooInDir(disk.mp))
232  if hint != None:
233  oldName=hint
234  else:
235  oldName=""
236  d=nameAdrive.nameAdriveDialog(self, oldName=oldName,
237  nameList=self.namesDialog.itemStrings(),
238  driveIdent=(stickId, uuid, tattoo))
239  d.show()
240  result=d.exec_()
241  return
242 
243  # Renvoie une fonction de rappel pour l'abonnement aux évènements de l'arrière-boutique.
244  # Il s'agit de la fonction pour les disques branchés
245  #
246 
247  def cbAdded(self):
248  def _cbAdded(man, obj):
249  if qApp.available.modified:
250  path=safePath(obj)
251  self.recentConnect=str(path)
252  delai=0.5 # petit délai pour que targets soit à jour
253  QTimer.singleShot(delai, self.deviceAdded)
254  qApp.available.modified=False
255  return _cbAdded
256 
257  # Renvoie une fonction de rappel pour l'abonnement aux évènements de l'arrière-boutique.
258  # Il s'agit de la fonction pour les disques débranchés
259  #
260 
261  def cbRemoved(self):
262  def _cbRemoved(man, obj):
263  if qApp.available.modified:
264  path=safePath(obj)
265  if path in qApp.available.targets:
267  delai=0.5 # petit délai pour que targets soit à jour
268  QTimer.singleShot(delai, self.deviceRemoved)
269  qApp.available.modified=False
270  return _cbRemoved
271 
272 
274 
275  def deviceAdded(self):
276  if self.recentConnect not in qApp.available.targets:
277  return
278  disk=qApp.available.targets[self.recentConnect]
279  if disk.parent: # c'est une partition
280  QTimer.singleShot(0, self.namingADrive)
281  self.findAllDisks()
282 
283 
285 
286  def deviceRemoved(self):
287  self.findAllDisks()
288 
289 
291 
292  def initRedoStuff(self):
293  # réserve les icônes
294  self.iconRedo = QIcon()
295  self.iconRedo.addPixmap(QIcon.fromTheme("go-jump").pixmap(32), QIcon.Normal, QIcon.Off)
296  self.iconStop = QIcon()
297  self.iconStop.addPixmap(QIcon.fromTheme("stop").pixmap(32), QIcon.Normal, QIcon.Off)
298  # réserve les phrases d'aide
299  self.redoToolTip=QApplication.translate("MainWindow", "Refaire à nouveau", None)
300  self.redoStatusTip=QApplication.translate("MainWindow", "Refaire à nouveau la dernière opération réussie, avec les baladeurs connectés plus récemment", None)
301  self.stopToolTip=QApplication.translate("MainWindow", "Arrêter les opérations en cours", None)
302  self.stopStatusTip=QApplication.translate("MainWindow", "Essaie d'arrêter les opérations en cours. À faire seulement si celles-ci durent trop longtemps", None)
303 
304 
306 
307  def applyPreferences(self):
308  prefs=db.readPrefs()
309  self.schoolFile=prefs["schoolFile"]
310  self.workdir=prefs["workdir"]
311  self.manFileLocation=prefs["manfile"]
312  self.mv=prefs["mv"]
313  self.header=ownedUsbDisk.uDisk2.headers()
314  self.findAllDisks()
315  return
316 
317  # Initialisation du catalogue des disques USB connectés, et
318  # maintenance de l'interface graphique.
319  # @param other un catalogue déjà tout prêt de disques (None par défaut)
320  #
321 
322  def findAllDisks(self, other=None):
323  if other:
324  qApp.available=other
325  else:
326  qApp.available=ownedUsbDisk.Available(access="firstFat")
327  self.connectTableModel(qApp.available)
328  connectedCount=int(qApp.available)
329  self.ui.lcdNumber.display(connectedCount)
330  self.t.resizeColumnsToContents()
331  self.updateButtons()
332  return
333 
334 
337 
338  def changeWd(self, newDir):
339  self.workdir=newDir
340  db.setWd(newDir)
341 
342 
345 
346  def tableClicked(self, idx):
347  c=idx.column()
348  mappedIdx=self.proxy.mapFromSource(idx)
349  r=mappedIdx.row()
350  h=self.header[c]
351  if c==0:
352  self.manageCheckBoxes()
353  pass
354  elif c==1:
355  # case du propriétaire
356  self.editOwner(mappedIdx)
357  elif "mp" in h:
358  cmd="xdg-open '%s'" %idx.data()
359  subprocess.call(cmd, shell=True)
360  elif "capacity" in h:
361  mount=idx.model().partition(idx).mountPoint()
362  dev,total,used,remain,pcent,path = self.diskSizeData(mount)
363  pcent=int(pcent[:-1])
364  w=diskFull.mainWindow(self,pcent,title=path, total=total, used=used)
365  w.show()
366  else:
367  QMessageBox.warning(None,
368  QApplication.translate("Dialog","Double-clic non pris en compte",None),
369  QApplication.translate("Dialog","pas d'action pour l'attribut {a}",None).format(a=h))
370 
371 
373 
374  def manageCheckBoxes(self):
375  cbDialog=checkBoxDialog.CheckBoxDialog(self)
376  cbDialog.exec_()
377 
378 
383 
384  def diskSizeData(self, rowOrDev):
385  if type(rowOrDev)==type(0):
386  path=qApp.available[rowOrDev][self.header.index("1mp")]
387  else:
388  path=rowOrDev
389  cmd ="df '%s'" %path
390  dfOutput=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0]
391  dfOutput=str(dfOutput.split(b"\n")[-2])
392  m = re.match("(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*", dfOutput).groups()
393  return m
394 
395 
396  # trouve le disque qui correspond à un propriétaire, ou alors
397  # renvoie le premier disque inconnu.
398  # @param student le propriétaire du disque
399  # @return le disque correspondant à l'étudiant
400  #
401 
402  def diskFromOwner(self,student):
403  defaultDisk=None
404  for d in ownedUsbDisk.Available(access="firstFat"):
405  s=db.readStudent(d.stickid, d.uuid, d.tattoo())
406  if s==student :
407  return d
408  elif s==None and defaultDisk==None :
409  # premier disque inconnu
410  defaultDisk=d
411  return defaultDisk
412 
413 
414 
417 
418  def editOwner(self, idx):
419  student="%s" %self.tm.data(idx,Qt.DisplayRole).value()
420  # on fait une modification dans la base de donnée des propriétaires de clés
421  ownedUsbDisk.editRecord(self.diskFromOwner(student), hint=student)
422  # après quoi on relit brutalement toute la list des clés connectées
423  self.findAllDisks()
424 
425  # Met à jour l'icône qui reflète la disponibilité de noms pour
426  # renommer automatiquement des baladeurs
427  # @param available vrai s'il y a des noms disponibles pour
428  # renommer des baladeurs.
429  #
430 
431  def setAvailableNames(self, available):
432  self.availableNames=available
433  if available:
434  icon=self.namesFullIcon
435  msg=self.namesFullTip
436  else:
437  icon=self.namesEmptyIcon
438  msg=self.namesEmptyTip
439  self.ui.namesButton.setIcon(icon)
440  self.ui.namesButton.setToolTip(msg)
441  self.ui.namesButton.setStatusTip(msg.replace("<br />",""))
442 
443  # Désactive ou active les flèches selon que l'option correspondante
444  # est possible ou non. Pour les flèches : ça aurait du sens de préparer
445  # une opération de copie avant même de brancher des clés, donc on les
446  # active. Par contre démonter les clés quand elles sont absentes ça
447  # n'a pas d'utilité.
448  # Change l'icône du dialogue des noms selon qu'il reste ou non des
449  # noms disponibles dans le dialogue des noms.
450  #
451 
452  def updateButtons(self):
453  global activeThreads, lastCommand
454  active = len(qApp.available)>0
455  for button in (self.ui.toButton,
456  self.ui.fromButton,
457  self.ui.delButton,
458  self.ui.umountButton):
459  button.setEnabled(active)
460  #modifie l'icone copyfrom/movefrom
461  if self.mv:
462  self.ui.fromButton.setIcon(self.movefromIcon)
463  else:
464  self.ui.fromButton.setIcon(self.copyfromIcon)
465  # l'état du redoButton dépend de plusieurs facteurs
466  # si un thread au moins est en cours, on y affiche un STOP actif
467  # sinon on y met l'icône de lastCommand, et celle-ci sera active
468  # seulement s'il y a une commande déjà validée
469  if len(activeThreads) > 0:
470  self.ui.redoButton.setIcon(self.iconStop)
471  self.ui.redoButton.setToolTip(self.stopToolTip)
472  self.ui.redoButton.setStatusTip(self.stopStatusTip)
473  self.ui.redoButton.setEnabled(True)
474  else:
475  self.oldThreads=set() # vide l'ensemble puisque tout est fini
476  self.ui.redoButton.setIcon(self.iconRedo)
477  self.ui.redoButton.setToolTip(self.redoToolTip)
478  self.ui.redoButton.setStatusTip(self.redoStatusTip)
479  self.ui.redoButton.setEnabled(lastCommand!=None)
480  l=self.namesDialog.ui.listWidget.findItems("*",Qt.MatchWildcard)
481  if len(l)>0:
482  self.ui.namesButton.setIcon(self.namesFullIcon)
483  else:
484  self.ui.namesButton.setIcon(self.namesEmptyIcon)
485 
486 
488 
489  def preference(self):
491  pref.setValues(db.readPrefs())
492  pref.show()
493  pref.exec_()
494  if pref.result()==QDialog.Accepted:
495  db.writePrefs(pref.values())
496  # on applique les préférences tout de suite sans redémarrer
497  self.applyPreferences()
498 
499 
501 
502  def delFiles(self):
503  titre1=QApplication.translate("Dialog","Choix de fichiers à supprimer",None)
504  titre2=QApplication.translate("Dialog","Choix de fichiers à supprimer (jokers autorisés)",None)
505  d=chooseInSticks.chooseDialog(self, titre1, titre2)
506  ok = d.exec_()
507  if ok:
508  pathList=d.pathList()
509  buttons=QMessageBox.Ok|QMessageBox.Cancel
510  defaultButton=QMessageBox.Cancel
511  reply=QMessageBox.warning(
512  None,
513  QApplication.translate("Dialog","Vous allez effacer plusieurs baladeurs",None),
514  QApplication.translate("Dialog","Etes-vous certain de vouloir effacer : "+"\n".join(pathList),None),
515  buttons, defaultButton)
516  if reply == QMessageBox.Ok:
517  cmd="usbThread.threadDeleteInUSB(p,{paths},subdir='Travail', logfile='{log}', parent=self)".format(paths=pathList,log=logFileName)
518  for p in qApp.available:
519  if not p.selected: continue # pas les médias désélectionnés
520  registerCmd(cmd,p)
521  t=eval(cmd)
522  t.setDaemon(True)
523  t.start()
524  self.oldThreads.add(t)
525  return True
526  else:
527  msgBox=QMessageBox.warning(
528  None,
529  QApplication.translate("Dialog","Aucun fichier sélectionné",None),
530  QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None))
531  return True
532 
533 
535 
536  def copyTo(self):
537  d=copyToDialog1.copyToDialog1(parent=self, workdir=self.workdir)
538  d.exec_()
539  if d.ok==True:
540  cmd="usbThread.threadCopyToUSB(p,{selected},subdir='{subdir}', logfile='{logfile}', parent=self)".format(selected=list(d.selectedList()), subdir=self.workdir, logfile=logFileName)
541 
542  for p in qApp.available:
543  if not p.selected: continue # pas les médias désélectionnés
544  registerCmd(cmd,p)
545  t=eval(cmd)
546  t.setDaemon(True)
547  t.start()
548  self.oldThreads.add(t)
549  return True
550  else:
551  msgBox=QMessageBox.warning(
552  None,
553  QApplication.translate("Dialog","Aucun fichier sélectionné",None),
554  QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None))
555  return True
556 
557 
559 
560  def copyFrom(self):
561  titre1=QApplication.translate("Dialog","Choix de fichiers à copier",None)
562  titre2=QApplication.translate("Dialog", "Choix de fichiers à copier depuis les baladeurs", None)
563  okPrompt=QApplication.translate("Dialog", "Choix de la destination ...", None)
564  d=chooseInSticks.chooseDialog(self, title1=titre1, title2=titre2, okPrompt=okPrompt)
565  d.exec_()
566  if not d.ok :
567  msgBox=QMessageBox.warning(None,
568  QApplication.translate("Dialog","Aucun fichier sélectionné",None),
569  QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None))
570  return True
571  # bon, alors c'est OK pour le choix des fichiers à envoyer
572  pathList=d.pathList()
573  mp=d.selectedDiskMountPoint()
574  initialPath=os.path.expanduser("~")
575  destDir = QFileDialog.getExistingDirectory(
576  None,
577  QApplication.translate("Dialog","Choisir un répertoire de destination",None),
578  initialPath)
579  if destDir and len(destDir)>0 :
580  if self.mv:
581  cmd="""usbThread.threadMoveFromUSB(
582  p,{paths},subdir=self.workdir,
583  rootPath='{mp}', dest='{dest}', logfile='{log}',
584  parent=self)""".format(paths=pathList, mp=mp, dest=destDir, log=logFileName)
585  else:
586  cmd="""usbThread.threadCopyFromUSB(
587  p,{paths},subdir=self.workdir,
588  rootPath='{mp}', dest='{dest}', logfile='{log}',
589  parent=self)""".format(paths=pathList, mp=mp, dest=destDir, log=logFileName)
590 
591  for p in qApp.available:
592  if not p.selected: continue # pas les médias désélectionnés
593  # on devrait vérifier s'il y a des données à copier
594  # et s'il n'y en a pas, ajouter des lignes au journal
595  # mais on va laisser faire ça dans le thread
596  # inconvénient : ça crée quelquefois des sous-répertoires
597  # vides inutiles dans le répertoire de destination.
598  registerCmd(cmd,p)
599  t=eval(cmd)
600  t.setDaemon(True)
601  t.start()
602  self.oldThreads.add(t)
603  # on ouvre un gestionnaire de fichiers pour voir le résultat
604  buttons=QMessageBox.Ok|QMessageBox.Cancel
605  defaultButton=QMessageBox.Cancel
606  if QMessageBox.question(
607  None,
608  QApplication.translate("Dialog","Voir les copies",None),
609  QApplication.translate("Dialog","Voulez-vous voir les fichiers copiés ?",None),
610  buttons, defaultButton)==QMessageBox.Ok:
611  subprocess.call("xdg-open '%s'" %destDir,shell=True)
612  return True
613  else:
614  msgBox=QMessageBox.warning(
615  None,
616  QApplication.translate("Dialog","Destination manquante",None),
617  QApplication.translate("Dialog","Veuillez choisir une destination pour la copie des fichiers",None))
618  return True
619 
620  # Relance la dernière commande, mais en l'appliquant seulement aux
621  # baladeurs nouvellement branchés.
622  #
623 
624  def redoCmd(self):
625  global lastCommand, pastCommands, activeThreads
626  if len(activeThreads)>0:
627  for thread in self.oldThreads:
628  if thread.isAlive():
629  try:
630  thread._Thread__stop()
631  print (str(thread.getName()) + ' is terminated')
632  except:
633  print (str(thread.getName()) + ' could not be terminated')
634  else:
635  if lastCommand==None:
636  return
637  if QMessageBox.question(
638  None,
639  QApplication.translate("Dialog","Réitérer la dernière commande",None),
640  QApplication.translate("Dialog","La dernière commande était<br>{cmd}<br>Voulez-vous la relancer avec les nouveaux baladeurs ?",None).format(cmd=lastCommand))==QMessageBox.Cancel:
641  return
642  for p in qApp.available:
643  if p.owner in pastCommands[lastCommand] : continue
644  exec(compile(lastCommand,'<string>','exec'))
645  t.setDaemon(True)
646  t.start()
647  self.oldThreads.add(t)
648  pastCommands[lastCommand].append(p.owner)
649 
650  # montre le dialogue de choix de nouveaux noms à partir d'un
651  # fichier administratif.
652  #
653 
654  def namesCmd(self):
655  self.namesDialog.show()
656 
657 
659 
660  def help(self):
661  w=help.helpWindow(self)
662  w.show()
663  w.exec_()
664 
665 
667 
668  def umount(self):
669  buttons=QMessageBox.Ok|QMessageBox.Cancel
670  defaultButton=QMessageBox.Cancel
671  button=QMessageBox.question (
672  self,
673  QApplication.translate("Main","Démontage des baladeurs",None),
674  QApplication.translate("Main","Êtes-vous sûr de vouloir démonter tous les baladeurs cochés de la liste ?",None),
675  buttons,defaultButton)
676  if button!=QMessageBox.Ok:
677  return
678  for d in qApp.available.disks_ud():
679  for partition in qApp.available.parts_ud(d.path):
680  if partition.mp:
681  cmd="umount {0}".format(partition.mp)
682  subprocess.call(cmd, shell=True)
683  cmd= "udisks --detach {0}".format(d.devStuff)
684  subprocess.call(cmd, shell=True)
685  self.findAllDisks() # remet à jour le compte de disques
686  self.operations=[] # remet à zéro la liste des opérations
687 
688 
689 
692 
693  def connectTableModel(self, data):
695  for h in self.header:
696  if h in ownedUsbDisk.uDisk2._itemNames:
697  self.visibleheader.append(self.tr(ownedUsbDisk.uDisk2._itemNames[h]))
698  else:
699  self.visibleheader.append(h)
700  self.tm=usbTableModel(self, self.visibleheader, data)
701  self.t.setModel(self.tm)
702  self.t.setItemDelegateForColumn(0, CheckBoxDelegate(self))
703  self.t.setItemDelegateForColumn(1, UsbDiskDelegate(self))
704  self.t.setItemDelegateForColumn(3, DiskSizeDelegate(self))
705  self.proxy.setSourceModel(self.t.model())
706 
707 
708 
710 
711  def sameDiskData(self, one, two):
712  return len(one.targets) == len(two.targets) and \
713  set([p.uniqueId() for p in one]) == set([p.uniqueId() for p in two])
714 
715 
717 
719 
720 
724 
725  def __init__(self, parent=None, header=[], donnees=None):
726  QAbstractTableModel.__init__(self,parent)
727  self.header=header
728  self.donnees=donnees
729  self.pere=parent
730 
731 
733 
734  def updateOwnerColumn(self):
735  column=1
736  self.dataChanged.emit(self.index(0,column), self.index(len(self.donnees)-1, column))
737  self.pere.t.viewport().update()
738 
739 
741 
742  def rowCount(self, parent):
743  return len(self.donnees)
744 
745 
747 
748  def columnCount(self, parent):
749  return len(self.header)
750 
751  def setData(self, index, value, role):
752  if index.column()==0:
753  self.donnees[index.row()].selected=value
754  return True
755  else:
756  return QAbstractTableModel.setData(self, index, role)
757 
758 
761 
762  def partition(self, index):
763  return self.donnees[index.row()][-1]
764 
765  def data(self, index, role):
766  if not index.isValid():
767  return QVariant()
768  elif role==Qt.ToolTipRole:
769  c=index.column()
770  h=self.pere.header[c]
771  if c==0:
772  return QApplication.translate("Main","Cocher ou décocher cette case en cliquant.<br><b>Double-clic</b> pour agir sur plusieurs baladeurs.",None)
773  elif c==1:
774  return QApplication.translate("Main","Propriétaire de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour modifier.",None)
775  elif "mp" in h:
776  return QApplication.translate("Main","Point de montage de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour voir les fichiers.",None)
777  elif "capacity" in h:
778  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)
779  elif "vendor" in h:
780  return QApplication.translate("Main","Fabricant de la clé USB ou du baladeur.",None)
781  elif "model" in h:
782  return QApplication.translate("Main","Modèle de la clé USB ou du baladeur.",None)
783  elif "stickid" in h:
784  return QApplication.translate("Main","Numéro de série de la clé USB ou du baladeur.",None)
785  else:
786  return ""
787  elif role != Qt.DisplayRole:
788  return QVariant()
789  if index.row()<len(self.donnees):
790  try:
791  return QVariant(self.donnees[index.row()][index.column()])
792  except KeyError:
793  print("Le bug du retrait de clé non détecté a encore frappé, quand sera-t-il éliminé ?")
794  self.pere.findAllDisks()
795  return QVariant("")
796 
797  else:
798  return QVariant()
799 
800  def headerData(self, section, orientation, role):
801  if orientation == Qt.Horizontal and role == Qt.DisplayRole:
802  return QVariant(self.header[section])
803  elif orientation == Qt.Vertical and role == Qt.DisplayRole:
804  return QVariant(section+1)
805  return QVariant()
806 
807 
811  def sort(self, Ncol, order=Qt.DescendingOrder):
812  self.layoutAboutToBeChanged.emit()
813  self.donnees = sorted(self.donnees, key=operator.itemgetter(Ncol))
814  if order == Qt.DescendingOrder:
815  self.donnees.reverse()
816  self.layoutChanged.emit()
817 
818 # @param view_item_style_options des options permettant de décider de
819 # la taille d'un rectangle
820 # @return un QRect dimensionné selon les bonnes options
821 #
822 
823 def CheckBoxRect(view_item_style_options):
824  check_box_style_option=QStyleOptionButton()
825  check_box_rect = QApplication.style().subElementRect(QStyle.SE_CheckBoxIndicator,check_box_style_option)
826  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)
827  return QRect(check_box_point, check_box_rect.size())
828 
830  def __init__(self, parent):
831  QStyledItemDelegate.__init__(self,parent)
832 
833  def paint(self, painter, option, index):
834  checked = bool(index.model().data(index, Qt.DisplayRole))
835  check_box_style_option=QStyleOptionButton()
836  check_box_style_option.state |= QStyle.State_Enabled
837  if checked:
838  check_box_style_option.state |= QStyle.State_On
839  else:
840  check_box_style_option.state |= QStyle.State_Off
841  check_box_style_option.rect = CheckBoxRect(option);
842  QApplication.style().drawControl(QStyle.CE_CheckBox, check_box_style_option, painter)
843 
844  def editorEvent(self, event, model, option, index):
845  if ((event.type() == QEvent.MouseButtonRelease) or (event.type() == QEvent.MouseButtonDblClick)):
846  if (event.button() != Qt.LeftButton or not CheckBoxRect(option).contains(event.pos())):
847  return False
848  if (event.type() == QEvent.MouseButtonDblClick):
849  return True
850  elif (event.type() == QEvent.KeyPress):
851  if event.key() != Qt.Key_Space and event.key() != Qt.Key_Select:
852  return False
853  else:
854  return False
855  checked = bool(index.model().data(index, Qt.DisplayRole))
856  result = model.setData(index, not checked, Qt.EditRole)
857  return result
858 
859 
860 # Classe pour identifier le baladeur dans le tableau.
861 # La routine de rendu à l'écran trace une petite icône et le nom du
862 # propriétaire à côté.
863 #
864 
866  def __init__(self, parent):
867  QStyledItemDelegate.__init__(self,parent)
868  self.okPixmap=QPixmap("/usr/share/icons/Tango/16x16/status/weather-clear.png")
869  self.busyPixmap=QPixmap("/usr/share/icons/Tango/16x16/actions/view-refresh.png")
870 
871  def paint(self, painter, option, index):
872  global activeThreads
873  text = index.model().data(index, Qt.DisplayRole).value()
874  rect0=QRect(option.rect)
875  rect1=QRect(option.rect)
876  h=rect0.height()
877  w=rect0.width()
878  rect0.setSize(QSize(h,h))
879  rect1.translate(h,0)
880  rect1.setSize(QSize(w-h,h))
881  QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette, True, text)
882  QApplication.style().drawItemText (painter, rect0, Qt.AlignCenter, option.palette, True, "O")
883  if text in activeThreads:
884  QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.busyPixmap)
885  else:
886  QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.okPixmap)
887 
888 # Classe pour figurer la taille de la mémoire du baladeur. Trace un petit
889 # secteur représentant la place occupée, puis affiche la place avec l'unité
890 # le plus parropriée.
891 #
892 
894  def __init__(self, parent):
895  QStyledItemDelegate.__init__(self,parent)
896 
897 
898  def paint(self, painter, option, index):
899  v=index.model().data(index, Qt.DisplayRole)
900  value = v.value()
901  text = self.val2txt(value)
902  rect0=QRect(option.rect)
903  rect1=QRect(option.rect)
904  rect0.translate(2,(rect0.height()-16)/2)
905  rect0.setSize(QSize(16,16))
906  rect1.translate(20,0)
907  rect1.setWidth(rect1.width()-20)
908  QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette, True, text)
909  # dessin d'un petit cercle pour l'occupation
910  mount=index.model().partition(index).mountPoint()
911  dev,total,used,remain,pcent,path = self.parent().diskSizeData(mount)
912  pcent=int(pcent[:-1])
913  painter.setBrush(QBrush(QColor("slateblue")))
914  painter.drawPie(rect0,0,16*360*pcent/100)
915 
916 
918 
919  def val2txt(self, val):
920  suffixes=["B", "KB", "MB", "GB", "TB"]
921  val*=1.0 # calcul flottant
922  i=0
923  while val > 1024 and i < len(suffixes):
924  i+=1
925  val/=1024
926  return "%4.1f %s" %(val, suffixes[i])
927 
src.mainWindow.mainWindow.copyFrom
def copyFrom(self)
Lance l'action de copier depuis les clés USB.
Definition: mainWindow.py:560
src.mainWindow.usbTableModel.header
header
Definition: mainWindow.py:727
src.mainWindow.usbTableModel.data
def data(self, index, role)
Definition: mainWindow.py:765
src.mainWindow.usbTableModel.headerData
def headerData(self, section, orientation, role)
Definition: mainWindow.py:800
src.usbDisk2.safePath
def safePath(obj)
Récupère de façon sûre le path d'une instance de UDisksObjectProxy.
Definition: usbDisk2.py:59
src.mainWindow.usbTableModel
Un modèle de table pour des séries de clés USB.
Definition: mainWindow.py:718
src.mainWindow.mainWindow.setAvailableNames
def setAvailableNames(self, available)
Definition: mainWindow.py:431
src.mainWindow.DiskSizeDelegate.__init__
def __init__(self, parent)
Definition: mainWindow.py:894
src.mainWindow.mainWindow.ui
ui
Definition: mainWindow.py:83
src.mainWindow.mainWindow.visibleheader
visibleheader
Definition: mainWindow.py:694
src.mainWindow.mainWindow.checkModify
def checkModify(self, boolFunc)
Definition: mainWindow.py:195
src.mainWindow.mainWindow.mv
mv
Definition: mainWindow.py:312
src.mainWindow.mainWindow.redoToolTip
redoToolTip
Definition: mainWindow.py:299
src.mainWindow.CheckBoxDelegate
Definition: mainWindow.py:829
src.mainWindow.mainWindow.iconRedo
iconRedo
Definition: mainWindow.py:294
src.copyToDialog1.copyToDialog1
Definition: copyToDialog1.py:36
src.mainWindow.mainWindow.recentConnect
recentConnect
Definition: mainWindow.py:103
src.mainWindow.mainWindow.redoStatusTip
redoStatusTip
Definition: mainWindow.py:300
src.mainWindow.mainWindow.namesDialog
namesDialog
Definition: mainWindow.py:102
src.mainWindow.DiskSizeDelegate.paint
def paint(self, painter, option, index)
Definition: mainWindow.py:898
src.mainWindow.mainWindow.applyPreferences
def applyPreferences(self)
Applique les préférences et les options de ligne de commande.
Definition: mainWindow.py:307
src.mainWindow.mainWindow.recentDisConnect
recentDisConnect
Definition: mainWindow.py:266
src.mainWindow.mainWindow.__init__
def __init__(self, parent, locale="fr_FR")
Le constructeur.
Definition: mainWindow.py:78
src.mainWindow.usbTableModel.sort
def sort(self, Ncol, order=Qt.DescendingOrder)
Sort table by given column number.
Definition: mainWindow.py:811
src.mainWindow.mainWindow.redoCmd
def redoCmd(self)
Definition: mainWindow.py:624
src.mainWindow.mainWindow.manageCheckBoxes
def manageCheckBoxes(self)
ouvre un dialogue pour permettre de gérer les cases à cocher globalement
Definition: mainWindow.py:374
src.mainWindow.mainWindow.checkAllSignal
checkAllSignal
custom signals ########################
Definition: mainWindow.py:66
src.mainWindow.UsbDiskDelegate.busyPixmap
busyPixmap
Definition: mainWindow.py:869
src.mainWindow.mainWindow.popCmd
def popCmd(self, owner, cmd)
fonction de rappel déclenchée par les threads (à la fin)
Definition: mainWindow.py:171
src.mainWindow.mainWindow.tm
tm
Definition: mainWindow.py:700
src.mainWindow.mainWindow.help
def help(self)
Affiche le widget d'aide.
Definition: mainWindow.py:660
src.mainWindow.CheckBoxDelegate.paint
def paint(self, painter, option, index)
Definition: mainWindow.py:833
src.mainWindow.usbTableModel.pere
pere
Definition: mainWindow.py:729
src.mainWindow.mainWindow.deviceRemoved
def deviceRemoved(self)
fonction de rappel pour un medium retiré ; se base sur la valeur de self.recentDisConnect
Definition: mainWindow.py:286
src.mainWindow.mainWindow.preference
def preference(self)
lance le dialogue des préférences
Definition: mainWindow.py:489
src.mainWindow.mainWindow.setThemedIcon
def setThemedIcon(self, button, name, default=None)
Associe une icone à un bouton, dans le thème courant.
Definition: mainWindow.py:143
QAbstractTableModel
src.mainWindow.mainWindow.namesEmptyTip
namesEmptyTip
Definition: mainWindow.py:101
src.mainWindow.mainWindow.copyfromIcon
copyfromIcon
Definition: mainWindow.py:87
src.ownedUsbDisk.Available
Definition: ownedUsbDisk.py:275
src.mainWindow.usbTableModel.donnees
donnees
Definition: mainWindow.py:728
src.mainWindow.mainWindow.updateButtons
def updateButtons(self)
Definition: mainWindow.py:452
src.mainWindow.mainWindow.cbRemoved
def cbRemoved(self)
Definition: mainWindow.py:261
src.mainWindow.mainWindow.diskSizeData
def diskSizeData(self, rowOrDev)
Definition: mainWindow.py:384
src.mainWindow.mainWindow.locale
locale
Definition: mainWindow.py:81
src.checkBoxDialog.CheckBoxDialog
Un dialogue pour gérer les cases à cocher de l'application.
Definition: checkBoxDialog.py:29
src.chooseInSticks.chooseDialog
Un dialogue pour choisir un ensemble de fichiers à copier depuis une clé USB.
Definition: chooseInSticks.py:33
src.mainWindow.usbTableModel.partition
def partition(self, index)
Definition: mainWindow.py:762
src.mainWindow.mainWindow.diskFromOwner
def diskFromOwner(self, student)
Definition: mainWindow.py:402
src.mainWindow.mainWindow.connectTableModel
def connectTableModel(self, data)
Connecte le modèle de table à la table.
Definition: mainWindow.py:693
src.mainWindow.CheckBoxDelegate.__init__
def __init__(self, parent)
Definition: mainWindow.py:830
src.mainWindow.mainWindow.umount
def umount(self)
Démonte et détache les clés USB affichées.
Definition: mainWindow.py:668
src.mainWindow.mainWindow.editOwner
def editOwner(self, idx)
Édition du propriétaire d'une clé.
Definition: mainWindow.py:418
src.mainWindow.mainWindow.checkToggle
def checkToggle(self)
Inverse la coche des baladeurs.
Definition: mainWindow.py:213
src.mainWindow.mainWindow.workdir
workdir
Definition: mainWindow.py:310
src.mainWindow.mainWindow.namesEmptyIcon
namesEmptyIcon
Definition: mainWindow.py:99
src.mainWindow.mainWindow.namingADrive
def namingADrive(self)
Definition: mainWindow.py:226
src.mainWindow.UsbDiskDelegate.__init__
def __init__(self, parent)
Definition: mainWindow.py:866
src.help.helpWindow
Definition: help.py:31
src.mainWindow.mainWindow.delFiles
def delFiles(self)
Lance l'action de supprimer des fichiers ou des répertoires dans les clés USB.
Definition: mainWindow.py:502
src.mainWindow.mainWindow.namesFullTip
namesFullTip
Definition: mainWindow.py:100
src.mainWindow.mainWindow.t
t
Definition: mainWindow.py:107
src.mainWindow.mainWindow.pushCmdSignal
pushCmdSignal
Definition: mainWindow.py:70
src.mainWindow.CheckBoxRect
def CheckBoxRect(view_item_style_options)
Definition: mainWindow.py:823
src.mainWindow.mainWindow.stopToolTip
stopToolTip
Definition: mainWindow.py:301
src.mainWindow.mainWindow.namesCmd
def namesCmd(self)
Definition: mainWindow.py:654
src.mainWindow.mainWindow.header
header
Definition: mainWindow.py:313
src.mainWindow.mainWindow.operations
operations
Definition: mainWindow.py:113
src.preferences.preferenceWindow
Definition: preferences.py:28
src.mainWindow.mainWindow.copyTo
def copyTo(self)
Lance l'action de copier vers les clés USB.
Definition: mainWindow.py:536
src.diskFull.mainWindow
Definition: diskFull.py:29
QStyledItemDelegate
src.mainWindow.mainWindow.findAllDisks
def findAllDisks(self, other=None)
Definition: mainWindow.py:322
src.mainWindow.usbTableModel.updateOwnerColumn
def updateOwnerColumn(self)
force la mise à jour de la colonne des propriétaires
Definition: mainWindow.py:734
QMainWindow
src.choixEleves.choixElevesDialog
Definition: choixEleves.py:38
src.mainWindow.mainWindow.schoolFile
schoolFile
Definition: mainWindow.py:309
src.mainWindow.mainWindow.shouldNameDrive
shouldNameDrive
Definition: mainWindow.py:69
src.mainWindow.mainWindow.checkNone
def checkNone(self)
Décoche tous les baladeurs.
Definition: mainWindow.py:219
src.mainWindow.mainWindow.manFileLocation
manFileLocation
Definition: mainWindow.py:311
src.mainWindow.mainWindow.cbAdded
def cbAdded(self)
Definition: mainWindow.py:247
src.mainWindow.mainWindow.deviceAdded
def deviceAdded(self)
Fonction de rappel pour un medium ajouté ; se base sur la valeur de self.recentConnect.
Definition: mainWindow.py:275
src.mainWindow.registerCmd
def registerCmd(cmd, partition)
enregistre la commande cmd pour la partition donnée
Definition: mainWindow.py:53
src.mainWindow.CheckBoxDelegate.editorEvent
def editorEvent(self, event, model, option, index)
Definition: mainWindow.py:844
QtCore
src.mainWindow.UsbDiskDelegate.okPixmap
okPixmap
Definition: mainWindow.py:868
src.mainWindow.usbTableModel.columnCount
def columnCount(self, parent)
@parent un QModelIndex
Definition: mainWindow.py:748
src.mainWindow.DiskSizeDelegate.val2txt
def val2txt(self, val)
Definition: mainWindow.py:919
src.mainWindow.mainWindow.initRedoStuff
def initRedoStuff(self)
Initialise des données pour le bouton central (refaire/stopper)
Definition: mainWindow.py:292
src.mainWindow.usbTableModel.rowCount
def rowCount(self, parent)
@parent un QModelIndex
Definition: mainWindow.py:742
src.mainWindow.mainWindow.tableClicked
def tableClicked(self, idx)
fonction de rappel pour un double clic sur un élément de la table
Definition: mainWindow.py:346
src.mainWindow.UsbDiskDelegate
Definition: mainWindow.py:865
src.mainWindow.mainWindow.stopStatusTip
stopStatusTip
Definition: mainWindow.py:302
src.mainWindow.mainWindow.iconStop
iconStop
Definition: mainWindow.py:296
src.mainWindow.mainWindow.pushCmd
def pushCmd(self, owner, cmd)
fonction de rappel déclenchée par les threads (au commencement)
Definition: mainWindow.py:157
src.mainWindow.mainWindow.availableNames
availableNames
Definition: mainWindow.py:432
src.mainWindow.mainWindow.checkAll
def checkAll(self)
Coche tous les baladeurs.
Definition: mainWindow.py:207
src.mainWindow.mainWindow.movefromIcon
movefromIcon
Definition: mainWindow.py:88
src.nameAdrive.nameAdriveDialog
Definition: nameAdrive.py:34
src.mainWindow.mainWindow.namesFullIcon
namesFullIcon
Definition: mainWindow.py:98
src.mainWindow.mainWindow.oldThreads
oldThreads
Definition: mainWindow.py:114
QtGui
src.mainWindow.mainWindow.proxy
proxy
Definition: mainWindow.py:108
src.mainWindow.usbTableModel.setData
def setData(self, index, value, role)
Definition: mainWindow.py:751
QtWidgets
src.mainWindow.usbTableModel.__init__
def __init__(self, parent=None, header=[], donnees=None)
Definition: mainWindow.py:725
src.mainWindow.DiskSizeDelegate
Definition: mainWindow.py:893
src.mainWindow.mainWindow.sameDiskData
def sameDiskData(self, one, two)
Definition: mainWindow.py:711
src.mainWindow.UsbDiskDelegate.paint
def paint(self, painter, option, index)
Definition: mainWindow.py:871
src.mainWindow.mainWindow.popCmdSignal
popCmdSignal
Definition: mainWindow.py:71
src.mainWindow.mainWindow.changeWd
def changeWd(self, newDir)
change le répertoire par défaut contenant les fichiers de travail
Definition: mainWindow.py:338
src.mainWindow.mainWindow.checkNoneSignal
checkNoneSignal
Definition: mainWindow.py:68
src.mainWindow.mainWindow.checkToggleSignal
checkToggleSignal
Definition: mainWindow.py:67
src.mainWindow.mainWindow
defines the main window of the application.
Definition: mainWindow.py:64