0
+
def run_device_job(self, slot, callable, *args, **kwargs):
'''
Run a job to communicate with the device.
@@ -53,7 +119,9 @@ class JobManager(QAbstractTableModel):
@param args: The arguments to pass to callable
@param kwargs: The keyword arguments to pass to callable
'''
- job = self.create_job(DeviceJob, self.device_lock, callable, *args, **kwargs)
+ desc = callable.__doc__ if callable.__doc__ else ''
+ desc += kwargs.pop('job_extra_description', '')
+ job = self.create_job(DeviceJob, desc, self.device_lock, callable, *args, **kwargs)
QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
self.job_done)
if slot:
@@ -68,15 +136,18 @@ class JobManager(QAbstractTableModel):
'''
self.job_remove_lock.lock()
try:
- job = self.jobs.pop(id)
+ job = self.jobs.pop(id)
+ self.reset()
self.cleanup_lock.lock()
self.cleanup[id] = job
self.cleanup_lock.unlock()
+ self.emit(SIGNAL('job_done(int)'), id)
if len(self.jobs.keys()) == 0:
self.emit(SIGNAL('no_more_jobs()'))
+
finally:
self.job_remove_lock.unlock()
-
+
def cleanup_jobs(self):
self.cleanup_lock.lock()
toast = []
@@ -86,4 +157,53 @@ class JobManager(QAbstractTableModel):
for id in toast:
self.cleanup.pop(id)
self.cleanup_lock.unlock()
+
+
+ def rowCount(self, parent):
+ return len(self.jobs)
+
+ def columnCount(self, parent):
+ return 3
+
+ def headerData(self, section, orientation, role):
+ if role != Qt.DisplayRole:
+ return NONE
+ if orientation == Qt.Horizontal:
+ if section == 0: text = "Job"
+ elif section == 1: text = "Status"
+ elif section == 2: text = "Progress"
+ return QVariant(self.trUtf8(text))
+ else:
+ return QVariant(section+1)
+
+ def data(self, index, role):
+ if role not in (Qt.DisplayRole, Qt.DecorationRole):
+ return NONE
+ row, col = index.row(), index.column()
+ keys = self.jobs.keys()
+ keys.sort()
+ job = self.jobs[keys[row]]
+ if role == Qt.DisplayRole:
+ if col == 0:
+ return QVariant('\n'.join(self.wrapper.wrap(job.description)))
+ if col == 1:
+ status = 'Waiting'
+ if job.isRunning():
+ status = 'Working'
+ if job.isFinished():
+ status = 'Done'
+ return QVariant(status)
+ if col == 2:
+ p = str(job.percent_done) + r'%'
+ return QVariant(p)
+ if role == Qt.DecorationRole and col == 0:
+ return self.device_job_icon if isinstance(job, DeviceJob) else self.job_icon
+ return NONE
+
+ def status_update(self, id, progress):
+ keys = self.jobs.keys()
+ keys.sort()
+ row = keys.index(id)
+ index = self.index(row, 2)
+ self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), index, index)
\ No newline at end of file
diff --git a/src/libprs500/gui2/main.py b/src/libprs500/gui2/main.py
index 5b81ce3cf3..b16bd9b0a2 100644
--- a/src/libprs500/gui2/main.py
+++ b/src/libprs500/gui2/main.py
@@ -16,7 +16,7 @@ import os, tempfile, sys
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QThread
-from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon
+from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox
from PyQt4.QtSvg import QSvgRenderer
from libprs500 import __version__, __appname__
@@ -32,6 +32,8 @@ from libprs500.gui2.status import StatusBar
from libprs500.gui2.jobs import JobManager, JobException
from libprs500.gui2.dialogs.metadata_single import MetadataSingleDialog
from libprs500.gui2.dialogs.metadata_bulk import MetadataBulkDialog
+from libprs500.gui2.dialogs.jobs import JobsDialog
+
class Main(QObject, Ui_MainWindow):
@@ -51,6 +53,7 @@ class Main(QObject, Ui_MainWindow):
self.setupUi(window)
self.read_settings()
self.job_manager = JobManager()
+ self.jobs_dialog = JobsDialog(self.window, self.job_manager)
self.device_manager = None
self.upload_memory = {}
self.delete_memory = {}
@@ -64,10 +67,10 @@ class Main(QObject, Ui_MainWindow):
self.vanity.setText(self.vanity_template.arg(' '))
####################### Status Bar #####################
- self.status_bar = StatusBar()
+ self.status_bar = StatusBar(self.jobs_dialog)
self.window.setStatusBar(self.status_bar)
QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added)
- QObject.connect(self.job_manager, SIGNAL('no_more_jobs()'), self.status_bar.no_more_jobs)
+ QObject.connect(self.job_manager, SIGNAL('job_done(int)'), self.status_bar.job_done)
####################### Setup Toolbar #####################
sm = QMenu()
@@ -195,6 +198,8 @@ class Main(QObject, Ui_MainWindow):
if exception:
self.job_exception(id, exception, formatted_traceback)
return
+ cp, fs = result
+ self.location_view.model().update_devices(cp, fs)
############################################################################
@@ -236,9 +241,11 @@ class Main(QObject, Ui_MainWindow):
Upload books to device.
@param files: List of either paths to files or file like objects
'''
+ titles = ', '.join([i['title'] for i in metadata])
id = self.job_manager.run_device_job(self.books_uploaded,
self.device_manager.upload_books_func(),
- files, names, on_card=on_card
+ files, names, on_card=on_card,
+ job_extra_description=titles
)
self.upload_memory[id] = metadata
@@ -434,8 +441,21 @@ class Main(QObject, Ui_MainWindow):
settings.endGroup()
def close_event(self, e):
- self.write_settings()
- e.accept()
+ msg = 'There are active jobs. Are you sure you want to quit?'
+ if self.job_manager.has_device_jobs():
+ msg = ''+__appname__ + ' is communicating with the device!
'+\
+ 'Quitting may cause corruption on the device.
'+\
+ 'Are you sure you want to quit?'
+ if self.job_manager.has_jobs():
+ d = QMessageBox(QMessageBox.Warning, 'WARNING: Active jobs', msg,
+ QMessageBox.Yes|QMessageBox.No, self.window)
+ d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
+ d.setDefaultButton(QMessageBox.No)
+ if d.exec_() == QMessageBox.Yes:
+ self.write_settings()
+ e.accept()
+ else:
+ e.ignore()
def main():
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
diff --git a/src/libprs500/gui2/status.py b/src/libprs500/gui2/status.py
index b7fd162c2c..6b0e8e8580 100644
--- a/src/libprs500/gui2/status.py
+++ b/src/libprs500/gui2/status.py
@@ -12,10 +12,14 @@
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+from libprs500.gui2.dialogs.jobs import JobsDialog
+
import textwrap
-from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap
+from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
+ QVBoxLayout, QSizePolicy
from PyQt4.QtCore import Qt, QSize
+from libprs500.gui2 import qstring_to_unicode
class BookInfoDisplay(QFrame):
class BookCoverDisplay(QLabel):
@@ -39,7 +43,7 @@ class BookInfoDisplay(QFrame):
def __init__(self):
QLabel.__init__(self)
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
- self.setText('')#
row 1 | row 2 |
fsfdsfsfsfsfsfsdfsffsfsd |
')
+ self.setText('')
def __init__(self, clear_message):
QFrame.__init__(self)
@@ -74,35 +78,83 @@ class BookInfoDisplay(QFrame):
self.clear_message()
self.setVisible(True)
-class MovieButton(QLabel):
- def __init__(self, movie):
+class BusyIndicator(QLabel):
+ def __init__(self, movie, jobs_dialog):
QLabel.__init__(self)
- self.movie = movie
+ self.setCursor(Qt.PointingHandCursor)
+ self.setToolTip('Click to see list of active jobs.')
self.setMovie(movie)
- self.movie.start()
- self.movie.setPaused(True)
+ movie.start()
+ movie.setPaused(True)
+ self.jobs_dialog = jobs_dialog
+
+
+ def mouseReleaseEvent(self, event):
+ if self.jobs_dialog.isVisible():
+ self.jobs_dialog.hide()
+ else:
+ self.jobs_dialog.show()
+
+
+class MovieButton(QFrame):
+ def __init__(self, movie, jobs_dialog):
+ QFrame.__init__(self)
+ self.setLayout(QVBoxLayout())
+ self.movie_widget = BusyIndicator(movie, jobs_dialog)
+ self.movie = movie
+ self.layout().addWidget(self.movie_widget)
+ self.jobs = QLabel('Jobs: 0')
+ self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
+ self.layout().addWidget(self.jobs)
+ self.layout().setAlignment(self.jobs, Qt.AlignHCenter)
+ self.jobs.setMargin(0)
+ self.layout().setMargin(0)
+ self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
+
class StatusBar(QStatusBar):
- def __init__(self):
+ def __init__(self, jobs_dialog):
QStatusBar.__init__(self)
- self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'))
+ self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), jobs_dialog)
self.addPermanentWidget(self.movie_button)
self.book_info = BookInfoDisplay(self.clearMessage)
self.addWidget(self.book_info)
+
+ def jobs(self):
+ src = qstring_to_unicode(self.movie_button.jobs.text())
+ return int(src.rpartition(':')[2].lstrip())
+
def job_added(self, id):
+ jobs = self.movie_button.jobs
+ src = qstring_to_unicode(jobs.text())
+ num = self.jobs()
+ nnum = num+1
+ text = src.replace(str(num), str(nnum))
+ jobs.setText(text)
if self.movie_button.movie.state() == QMovie.Paused:
self.movie_button.movie.setPaused(False)
+ def job_done(self, id):
+ jobs = self.movie_button.jobs
+ src = qstring_to_unicode(jobs.text())
+ num = self.jobs()
+ nnum = num-1
+ text = src.replace(str(num), str(nnum))
+ jobs.setText(text)
+ if nnum == 0:
+ self.no_more_jobs()
+
def no_more_jobs(self):
if self.movie_button.movie.state() == QMovie.Running:
self.movie_button.movie.setPaused(True)
- self.movie_button.movie.jumpToFrame(0) # This causes MNG error 11, but seems to work regardless
+ # This causes MNG error 11
+ #self.movie_button.movie.jumpToFrame(0)
if __name__ == '__main__':
# Used to create the animated status icon
- from PyQt4.Qt import QApplication, QPainter, QSvgRenderer, QPixmap, QColor
+ from PyQt4.Qt import QApplication, QPainter, QSvgRenderer, QColor
from subprocess import check_call
import os
app = QApplication([])