00001
00002
00003
00004
00005 licence={}
00006 licence['en']="""
00007 file mainWindow.py
00008 this file is part of the project scolasync
00009
00010 Copyright (C) 2010 Georges Khaznadar <georgesk@ofset.org>
00011
00012 This program is free software: you can redistribute it and/or modify
00013 it under the terms of the GNU General Public License as published by
00014 the Free Software Foundation, either version3 of the License, or
00015 (at your option) any later version.
00016
00017 This program is distributed in the hope that it will be useful,
00018 but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00020 GNU General Public License for more details.
00021
00022 You should have received a copy of the GNU General Public License
00023 along with this program. If not, see <http://www.gnu.org/licenses/>.
00024 """
00025
00026 from PyQt4.QtCore import *
00027 from PyQt4.QtGui import *
00028 import ownedUsbDisk, help, copyToDialog1, chooseInSticks, usbThread
00029 import diskFull, preferences
00030 import os.path, operator, subprocess, dbus, re, time, copy
00031 from notification import Notification
00032 import db
00033 from globaldef import logFileName
00034
00035
00036 qApp.diskData=ownedUsbDisk.Available(True,access="firstFat")
00037
00038
00039
00040
00041
00042
00043 def firstdir(l):
00044 for d in l:
00045 if os.path.isdir(d): return d
00046 return None
00047
00048
00049
00050
00051
00052
00053 def _dir(which):
00054 if which=="lang":
00055 return firstdir(["/usr/share/scolasync/lang", "lang"])
00056 elif which=="help":
00057 return firstdir(["/usr/share/scolasync/help", "help"])
00058 elif which=="share":
00059 return firstdir(["/usr/share/scolasync/","share"])
00060 return None
00061
00062
00063 class mainWindow(QMainWindow):
00064
00065
00066
00067
00068
00069
00070
00071 def __init__(self, parent, opts, locale="fr_FR"):
00072 QMainWindow.__init__(self)
00073 QWidget.__init__(self, parent)
00074 self.locale=locale
00075 from Ui_mainWindow import Ui_MainWindow
00076 self.ui = Ui_MainWindow()
00077 self.ui.setupUi(self)
00078 self.t=self.ui.tableView
00079 self.opts=opts
00080 self.applyPreferences()
00081 self.timer=QTimer()
00082 self.updateButtons()
00083 self.oldThreads=set()
00084 self.flashTimer=QTimer()
00085 self.flashTimer.setSingleShot(True)
00086 QObject.connect(self.ui.forceCheckButton, SIGNAL("clicked()"), self.checkDisks)
00087 QObject.connect(self.timer, SIGNAL("timeout()"), self.checkDisks)
00088 QObject.connect(self.flashTimer, SIGNAL("timeout()"), self.normalLCD);
00089 QObject.connect(self.ui.helpButton, SIGNAL("clicked()"), self.help)
00090 QObject.connect(self.ui.umountButton, SIGNAL("clicked()"), self.umount)
00091 QObject.connect(self.ui.toButton, SIGNAL("clicked()"), self.copyTo)
00092 QObject.connect(self.ui.fromButton, SIGNAL("clicked()"), self.copyFrom)
00093 QObject.connect(self.ui.delButton, SIGNAL("clicked()"), self.delFiles)
00094 QObject.connect(self.ui.preferenceButton, SIGNAL("clicked()"), self.preference)
00095 QObject.connect(self.ui.tableView, SIGNAL("doubleClicked(const QModelIndex&)"), self.tableClicked)
00096
00097
00098
00099
00100
00101
00102
00103 def showEvent (self, ev):
00104 result=QMainWindow.showEvent(self, ev)
00105 self.timer.start(20000)
00106 self.checkDisks(force=True)
00107 return result
00108
00109
00110
00111
00112
00113 def applyPreferences(self):
00114 prefs=db.readPrefs()
00115 self.workdir=prefs["workdir"]
00116
00117
00118 self.checkable=("--check","") in self.opts or ("-c","") in self.opts or prefs["checkable"]
00119 other=ownedUsbDisk.Available(self.checkable,access="firstFat")
00120 qApp.diskData=other
00121 self.header=ownedUsbDisk.uDisk.headers(self.checkable)
00122 self.connectTableModel(other)
00123
00124 self.t.setSortingEnabled(True)
00125 self.t.resizeColumnsToContents()
00126
00127
00128
00129
00130
00131
00132 def changeWd(self, newDir):
00133 self.workdir=newDir
00134 db.setWd(newDir)
00135
00136
00137
00138
00139
00140
00141 def tableClicked(self, idx):
00142 c=idx.column()
00143 r=idx.row()
00144 h=self.header[c]
00145 if c==0 and self.checkable:
00146
00147 pass
00148 elif c==1:
00149
00150 self.editOwner(r)
00151 elif "device-mount-paths" in h:
00152 cmd=u"nautilus '%s'" %idx.data().toString ()
00153 subprocess.call(cmd, shell=True)
00154 elif "device-size" in h:
00155 mount=idx.model().partition(idx).mountPoint()
00156 dev,total,used,remain,pcent,path = self.diskSizeData(mount)
00157 pcent=int(pcent[:-1])
00158 w=diskFull.mainWindow(self,pcent,title=path, total=total, used=used)
00159 w.show()
00160 else:
00161 QMessageBox.warning(None,
00162 QApplication.translate("Dialog","Double-clic non pris en compte",None, QApplication.UnicodeUTF8),
00163 QApplication.translate("Dialog","pas d'action pour l'attribut %1",None, QApplication.UnicodeUTF8).arg(h))
00164
00165
00166
00167
00168
00169
00170
00171
00172 def diskSizeData(self, rowOrDev):
00173 if type(rowOrDev)==type(0):
00174 path=qApp.diskData[rowOrDev][self.header.index("1device-mount-paths")]
00175 else:
00176 path=rowOrDev
00177 cmd =u"df '%s'" %path
00178 dfOutput=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()[0].split("\n")[-2]
00179 m = re.match("(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*", dfOutput).groups()
00180 return m
00181
00182
00183
00184
00185
00186
00187
00188
00189 def diskFromTableRow(self,ligne):
00190 stickid=qApp.diskData[ligne][self.header.index("5drive-serial")]
00191 found=False
00192 for d in qApp.diskData.disks.keys():
00193 if d.showableProp("drive-serial")==stickid:
00194 found=True
00195 break
00196 if found:
00197 return d
00198 else:
00199 return None
00200
00201
00202
00203
00204
00205
00206 def editOwner(self, ligne):
00207 student=qApp.diskData[ligne][1]
00208 ownedUsbDisk.editRecord(self.diskFromTableRow(ligne), student)
00209 other=ownedUsbDisk.Available(self.checkable,access="firstFat")
00210 qApp.diskData=other
00211 self.connectTableModel(other)
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221 def updateButtons(self):
00222 if len(qApp.diskData)>0:
00223 self.ui.toButton.setEnabled(True)
00224 self.ui.fromButton.setEnabled(True)
00225 self.ui.umountButton.setEnabled(True)
00226 else:
00227 self.ui.toButton.setEnabled(False)
00228 self.ui.fromButton.setEnabled(False)
00229 self.ui.umountButton.setEnabled(False)
00230
00231
00232
00233
00234
00235 def preference(self):
00236 pref=preferences.preferenceWindow()
00237 pref.setValues(db.readPrefs())
00238 pref.show()
00239 pref.exec_()
00240 if pref.result()==QDialog.Accepted:
00241 db.writePrefs(pref.values())
00242
00243 self.applyPreferences()
00244
00245
00246
00247
00248
00249 def delFiles(self):
00250 titre1=QApplication.translate("Dialog","Choix de fichiers à supprimer",None, QApplication.UnicodeUTF8)
00251 titre2=QApplication.translate("Dialog","Choix de fichiers à supprimer (jokers autorisés)",None, QApplication.UnicodeUTF8)
00252 d=chooseInSticks.chooseDialog(self, titre1, titre2)
00253 ok = d.exec_()
00254 if ok:
00255 pathList=map(lambda x: u"%s" %x, d.pathList())
00256 reply=QMessageBox.critical(None,
00257 QApplication.translate("Dialog","Vous allez effacer plusieurs baladeurs",None, QApplication.UnicodeUTF8),
00258 QApplication.translate("Dialog","Etes-vous certain de vouloir effacer : "+",".join(pathList),None, QApplication.UnicodeUTF8))
00259 if reply == QMessageBox.Ok:
00260 for p in qApp.diskData:
00261 t=usbThread.threadDeleteInUSB(p,pathList,subdir="Travail", logfile=logFileName)
00262 t.setDaemon(True)
00263 t.start()
00264 self.checkDisks()
00265 else:
00266 msgBox=QMessageBox.warning(None,
00267 QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
00268 QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))
00269
00270
00271
00272
00273
00274 def copyTo(self):
00275 d=copyToDialog1.copyToDialog1(parent=self, workdir=self.workdir)
00276 d.exec_()
00277 if d.ok==True:
00278 for p in qApp.diskData:
00279 subdir=self.workdir
00280 t=usbThread.threadCopyToUSB(p,d.selectedList(),subdir=subdir, logfile=logFileName)
00281 t.setDaemon(True)
00282 t.start()
00283 self.checkDisks()
00284 else:
00285 msgBox=QMessageBox.warning(None,
00286 QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
00287 QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))
00288
00289
00290
00291
00292
00293 def copyFrom(self):
00294 titre1=QApplication.translate("Dialog","Choix de fichiers à copier",None, QApplication.UnicodeUTF8)
00295 titre2=QApplication.translate("Dialog", "Choix de fichiers à copier depuis les baladeurs", None, QApplication.UnicodeUTF8)
00296 ok=QApplication.translate("Dialog", "Choix de la destination ...", None, QApplication.UnicodeUTF8)
00297 d=chooseInSticks.chooseDialog(self, title1=titre1, title2=titre2, ok=ok)
00298 ok = d.exec_()
00299 if not ok or len(d.pathList())==0 :
00300 msgBox=QMessageBox.warning(None,
00301 QApplication.translate("Dialog","Aucun fichier sélectionné",None, QApplication.UnicodeUTF8),
00302 QApplication.translate("Dialog","Veuillez choisir au moins un fichier",None, QApplication.UnicodeUTF8))
00303 return
00304
00305 pathList=map(lambda x: u"%s" %x, d.pathList())
00306 mp=d.selectedDiskMountPoint()
00307 initialPath=os.path.expanduser("~")
00308 destDir = QFileDialog.getExistingDirectory(None,
00309 QApplication.translate("Dialog","Choisir un répertoire de destination",None, QApplication.UnicodeUTF8),
00310 initialPath)
00311 if destDir and len(destDir)>0 :
00312 dest=u"%s" %destDir
00313 for p in qApp.diskData:
00314
00315
00316
00317
00318
00319 t=usbThread.threadCopyFromUSB(p,pathList,subdir=self.workdir,
00320 rootPath=mp,
00321 dest=dest,
00322 logfile=logFileName)
00323 t.setDaemon(True)
00324 t.start()
00325 self.checkDisks()
00326
00327 if QMessageBox.question(None,
00328 QApplication.translate("Dialog","Voir les copies",None, QApplication.UnicodeUTF8),
00329 QApplication.translate("Dialog","Voulez-vous voir les fichiers copiés ?",None, QApplication.UnicodeUTF8)):
00330 subprocess.call("nautilus '%s'" %dest,shell=True)
00331 else:
00332 msgBox=QMessageBox.warning(None,
00333 QApplication.translate("Dialog","Destination manquante",None, QApplication.UnicodeUTF8),
00334 QApplication.translate("Dialog","Veuillez choisir une destination pour la copie des fichiers",None, QApplication.UnicodeUTF8))
00335
00336
00337
00338
00339
00340
00341
00342 def help(self):
00343 w=help.helpWindow(None)
00344 w.loadBrowsers(_dir("help"),self.locale)
00345 w.show()
00346 w.exec_()
00347
00348
00349
00350
00351
00352 def umount(self):
00353 buttons=QMessageBox.StandardButtons(QMessageBox.Ok+QMessageBox.Cancel)
00354 button=QMessageBox.critical ( self,
00355 QApplication.translate("Main","Démontage des baladeurs",None, QApplication.UnicodeUTF8),
00356 QApplication.translate("Main","Êtes-vous sûr de vouloir démonter tous les baladeurs cochés de la liste ?",None, QApplication.UnicodeUTF8),
00357 buttons=buttons)
00358 if button!=QMessageBox.Ok:
00359 return
00360
00361 for p in qApp.diskData:
00362
00363 for d in qApp.diskData.disks.keys():
00364 if p in qApp.diskData.disks[d] and p.selected:
00365
00366 for partition in qApp.diskData.disks[d]:
00367 devfile=partition.getProp("device-file-by-id")
00368 if isinstance(devfile, dbus.Array):
00369 devfile=devfile[0]
00370 subprocess.call("udisks --unmount %s" %devfile, shell=True)
00371
00372 devfile_disk=d.getProp("device-file-by-id")
00373 if isinstance(devfile_disk, dbus.Array):
00374 devfile_disk=devfile_disk[0]
00375 subprocess.call("udisks --detach %s" %devfile_disk, shell=True)
00376 break
00377 self.checkDisks()
00378
00379
00380
00381
00382
00383
00384
00385 def connectTableModel(self, data):
00386 self.visibleheader=[]
00387 for h in self.header:
00388 if h in ownedUsbDisk.uDisk._itemNames:
00389 self.visibleheader.append(self.tr(ownedUsbDisk.uDisk._itemNames[h]))
00390 else:
00391 self.visibleheader.append(h)
00392 self.tm=usbTableModel(self, self.visibleheader,data,self.checkable)
00393 self.t.setModel(self.tm)
00394 if self.checkable:
00395 self.t.setItemDelegateForColumn(0, CheckBoxDelegate(self))
00396 self.t.setItemDelegateForColumn(1, UsbDiskDelegate(self))
00397 self.t.setItemDelegateForColumn(3, DiskSizeDelegate(self))
00398 else:
00399 self.t.setItemDelegateForColumn(0, UsbDiskDelegate(self))
00400 self.t.setItemDelegateForColumn(2, DiskSizeDelegate(self))
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413 def checkDisks(self, force=False):
00414 other=ownedUsbDisk.Available(self.checkable,access="firstFat")
00415 if force or not self.sameDiskData(qApp.diskData, other):
00416 qApp.diskData=other
00417 self.oldThreads=copy.copy(usbThread.globalThreads.threadSet())
00418 connectedCount=int(other)
00419 self.connectTableModel(other)
00420 self.updateButtons()
00421 self.t.resizeColumnsToContents()
00422 self.ui.lcdNumber.display(connectedCount)
00423 self.flashLCD()
00424 self.t.sortByColumn(2)
00425
00426
00427
00428
00429
00430 def sameDiskData(self, one, two):
00431 return set([p.uniqueId() for p in one]) == set([p.uniqueId() for p in two]) and self.oldThreads == usbThread.globalThreads.threadSet()
00432
00433
00434
00435
00436
00437 def flashLCD(self):
00438 self.ui.lcdNumber.setBackgroundRole(QPalette.Highlight)
00439 self.flashTimer.start(250)
00440
00441
00442
00443
00444
00445 def normalLCD(self):
00446 self.ui.lcdNumber.setBackgroundRole(QPalette.Window)
00447
00448
00449
00450
00451
00452 class usbTableModel(QAbstractTableModel):
00453
00454
00455
00456
00457
00458
00459
00460
00461 def __init__(self, parent=None, header=[], donnees=None, checkable=False):
00462 QAbstractTableModel.__init__(self,parent)
00463 self.header=header
00464 self.donnees=donnees
00465 self.checkable=checkable
00466 self.pere=parent
00467
00468
00469
00470
00471
00472 def rowCount(self, parent):
00473 return len(self.donnees)
00474
00475
00476
00477
00478
00479 def columnCount(self, parent):
00480 return len(self.header)
00481
00482 def setData(self, index, value, role):
00483 if index.column()==0 and self.checkable:
00484 self.donnees[index.row()].selected=value
00485 return True
00486 else:
00487 return QAbstractTableModel.setData(self, index, role)
00488
00489
00490
00491
00492
00493
00494 def partition(self, index):
00495 return self.donnees[index.row()][-1]
00496
00497 def data(self, index, role):
00498 if not index.isValid():
00499 return QVariant()
00500 elif role==Qt.ToolTipRole:
00501 c=index.column()
00502 h=self.pere.header[c]
00503 if c==0 and self.checkable:
00504 return QApplication.translate("Main","Cocher ou décocher cette case en cliquant.",None, QApplication.UnicodeUTF8)
00505 elif c==1:
00506 return QApplication.translate("Main","Propriétaire de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour modifier.",None, QApplication.UnicodeUTF8)
00507 elif "device-mount-paths" in h:
00508 return QApplication.translate("Main","Point de montage de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour voir les fichiers.",None, QApplication.UnicodeUTF8)
00509 elif "device-size" in h:
00510 return QApplication.translate("Main","Capacité de la clé USB ou du baladeur en kO ;<br><b>Double-clic</b> pour voir la place occupée.",None, QApplication.UnicodeUTF8)
00511 elif "drive-vendor" in h:
00512 return QApplication.translate("Main","Fabricant de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
00513 elif "drive-model" in h:
00514 return QApplication.translate("Main","Modèle de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
00515 elif "drive-serial" in h:
00516 return QApplication.translate("Main","Numéro de série de la clé USB ou du baladeur.",None, QApplication.UnicodeUTF8)
00517 else:
00518 return ""
00519 elif role != Qt.DisplayRole:
00520 return QVariant()
00521 if index.row()<len(self.donnees):
00522 return QVariant(self.donnees[index.row()][index.column()])
00523 else:
00524 return QVariant()
00525
00526 def headerData(self, section, orientation, role):
00527 if orientation == Qt.Horizontal and role == Qt.DisplayRole:
00528 return QVariant(self.header[section])
00529 elif orientation == Qt.Vertical and role == Qt.DisplayRole:
00530 return QVariant(section+1)
00531 return QVariant()
00532
00533
00534
00535
00536
00537
00538 def sort(self, Ncol, order=Qt.DescendingOrder):
00539 self.emit(SIGNAL("layoutAboutToBeChanged()"))
00540 self.donnees = sorted(self.donnees, key=operator.itemgetter(Ncol))
00541 if order == Qt.DescendingOrder:
00542 self.donnees.reverse()
00543 self.emit(SIGNAL("layoutChanged()"))
00544
00545 def CheckBoxRect(view_item_style_options):
00546 check_box_style_option=QStyleOptionButton()
00547 check_box_rect = QApplication.style().subElementRect(QStyle.SE_CheckBoxIndicator,check_box_style_option)
00548 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)
00549 return QRect(check_box_point, check_box_rect.size())
00550
00551 class CheckBoxDelegate(QStyledItemDelegate):
00552 def __init__(self, parent):
00553 QStyledItemDelegate.__init__(self,parent)
00554
00555 def paint(self, painter, option, index):
00556 checked = index.model().data(index, Qt.DisplayRole).toBool()
00557 check_box_style_option=QStyleOptionButton()
00558 check_box_style_option.state |= QStyle.State_Enabled
00559 if checked:
00560 check_box_style_option.state |= QStyle.State_On
00561 else:
00562 check_box_style_option.state |= QStyle.State_Off
00563 check_box_style_option.rect = CheckBoxRect(option);
00564 QApplication.style().drawControl(QStyle.CE_CheckBox, check_box_style_option, painter)
00565
00566 def editorEvent(self, event, model, option, index):
00567 if ((event.type() == QEvent.MouseButtonRelease) or (event.type() == QEvent.MouseButtonDblClick)):
00568 if (event.button() != Qt.LeftButton or not CheckBoxRect(option).contains(event.pos())):
00569 return False
00570 if (event.type() == QEvent.MouseButtonDblClick):
00571 return True
00572 elif (event.type() == QEvent.KeyPress):
00573 if event.key() != Qt.Key_Space and event.key() != Qt.Key_Select:
00574 return False
00575 else:
00576 return False
00577 checked = index.model().data(index, Qt.DisplayRole).toBool()
00578 result = model.setData(index, not checked, Qt.EditRole)
00579 return result
00580
00581
00582 class UsbDiskDelegate(QStyledItemDelegate):
00583 def __init__(self, parent):
00584 QStyledItemDelegate.__init__(self,parent)
00585 self.okPixmap=QPixmap("/usr/share/icons/Tango/16x16/status/weather-clear.png")
00586 self.busyPixmap=QPixmap("/usr/share/icons/Tango/16x16/actions/view-refresh.png")
00587
00588 def paint(self, painter, option, index):
00589 text = index.model().data(index, Qt.DisplayRole).toString()
00590 rect0=QRect(option.rect)
00591 rect1=QRect(option.rect)
00592 h=rect0.height()
00593 w=rect0.width()
00594 rect0.setSize(QSize(h,h))
00595 rect1.translate(h,0)
00596 rect1.setSize(QSize(w-h,h))
00597 QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette, True, text)
00598 QApplication.style().drawItemText (painter, rect0, Qt.AlignCenter, option.palette, True, QString("O"))
00599 if usbThread.globalThreads.busy(u"%s" %text):
00600 QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.busyPixmap)
00601 else:
00602 QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.okPixmap)
00603
00604 class DiskSizeDelegate(QStyledItemDelegate):
00605 def __init__(self, parent):
00606 QStyledItemDelegate.__init__(self,parent)
00607
00608
00609 def paint(self, painter, option, index):
00610 value = int(index.model().data(index, Qt.DisplayRole).toString())
00611 text = self.val2txt(value)
00612 rect0=QRect(option.rect)
00613 rect1=QRect(option.rect)
00614 rect0.translate(2,(rect0.height()-16)/2)
00615 rect0.setSize(QSize(16,16))
00616 rect1.translate(20,0)
00617 rect1.setWidth(rect1.width()-20)
00618 QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette, True, text)
00619
00620 mount=index.model().partition(index).mountPoint()
00621 dev,total,used,remain,pcent,path = self.parent().diskSizeData(mount)
00622 pcent=int(pcent[:-1])
00623 painter.setBrush(QBrush(QColor("slateblue")))
00624 painter.drawPie(rect0,0,16*360*pcent/100)
00625
00626
00627
00628
00629
00630 def val2txt(self, val):
00631 suffixes=["B", "KB", "MB", "GB", "TB"]
00632 val*=1.0
00633 i=0
00634 while val > 1024 and i < len(suffixes):
00635 i+=1
00636 val/=1024
00637 return "%4.1f %s" %(val, suffixes[i])
00638
00639