ScolaSync: Fichier source de src/mainWindow.py

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
64 class mainWindow(QMainWindow):
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)
102 self.namesDialog=choixEleves.choixElevesDialog(parent =self)
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:
266 self.recentDisConnect=path
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):
490 pref=preferences.preferenceWindow()
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):
694 self.visibleheader=[]
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
718 class usbTableModel(QAbstractTableModel):
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
829 class CheckBoxDelegate(QStyledItemDelegate):
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
865 class UsbDiskDelegate(QStyledItemDelegate):
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
893 class DiskSizeDelegate(QStyledItemDelegate):
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