From d2fe0bf7ee71c8b4660f36aa01cf4113b8a7e33c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 15:41:24 -0600 Subject: [PATCH] Directly draw the spinner instead of using an animated GIF --- resources/images/spinner.gif | Bin 10848 -> 0 bytes src/calibre/gui2/metadata/single_download.py | 256 ++++++++++++++++--- 2 files changed, 225 insertions(+), 31 deletions(-) delete mode 100644 resources/images/spinner.gif diff --git a/resources/images/spinner.gif b/resources/images/spinner.gif deleted file mode 100644 index 5e864f2e37d51e2b45731822225161780027ea2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10848 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 diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 2c9b234fba..c397a8660a 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -9,12 +9,13 @@ __docformat__ = 'restructuredtext en' from threading import Thread, Event from operator import attrgetter +from Queue import Queue, Empty from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette, QTimer, pyqtSignal, QAbstractTableModel, QVariant, QSize, QListView, - QPixmap, QAbstractListModel, QMovie) + QPixmap, QAbstractListModel, QColor, QRect) from PyQt4.QtWebKit import QWebView from calibre.customize.ui import metadata_plugins @@ -25,6 +26,9 @@ from calibre.ebooks.metadata.book.base import Metadata from calibre.gui2 import error_dialog, NONE from calibre.utils.date import utcnow, fromordinal, format_date from calibre.library.comments import comments_to_html +from calibre import force_unicode + +DEVELOP_DIALOG = False class RichTextDelegate(QStyledItemDelegate): # {{{ @@ -269,7 +273,7 @@ class IdentifyWorker(Thread): # {{{ def run(self): try: - if True: + if DEVELOP_DIALOG: self.results = self.sample_results() else: self.results = identify(self.log, self.abort, title=self.title, @@ -278,7 +282,7 @@ class IdentifyWorker(Thread): # {{{ result.gui_rank = i except: import traceback - self.error = traceback.format_exc() + self.error = force_unicode(traceback.format_exc()) # }}} class IdentifyWidget(QWidget): # {{{ @@ -399,9 +403,39 @@ class IdentifyWidget(QWidget): # {{{ self.abort.set() # }}} +class CoverWorker(Thread): # {{{ + + def __init__(self, log, abort, title, authors, identifiers): + Thread.__init__(self) + self.daemon = True + + self.log, self.abort = log, abort + self.title, self.authors, self.identifiers = (title, authors, + identifiers) + + self.rq = Queue() + self.error = None + + def fake_run(self): + import time + time.sleep(2) + + def run(self): + try: + if DEVELOP_DIALOG: + self.fake_run() + else: + from calibre.ebooks.metadata.sources.covers import run_download + run_download(self.log, self.rq, self.abort, title=self.title, + authors=self.authors, identifiers=self.identifiers) + except: + import traceback + self.error = force_unicode(traceback.format_exc()) +# }}} + class CoversModel(QAbstractListModel): # {{{ - def __init__(self, log, current_cover, parent=None): + def __init__(self, current_cover, parent=None): QAbstractListModel.__init__(self, parent) if current_cover is None: @@ -409,13 +443,14 @@ class CoversModel(QAbstractListModel): # {{{ self.blank = QPixmap(I('blank.png')).scaled(150, 200) - self.covers = [self.get_item(_('Current cover'), current_cover, False)] - for plugin in metadata_plugins(['cover']): + self.covers = [self.get_item(_('Current cover'), current_cover)] + self.plugin_map = {} + for i, plugin in enumerate(metadata_plugins(['cover'])): self.covers.append((plugin.name+'\n'+_('Searching...'), QVariant(self.blank), None, True)) - self.log = log + self.plugin_map[plugin] = i+1 - def get_item(self, src, pmap, waiting=True): + def get_item(self, src, pmap, waiting=False): sz = '%dx%d'%(pmap.width(), pmap.height()) text = QVariant(src + '\n' + sz) scaled = pmap.scaled(150, 200, Qt.IgnoreAspectRatio, @@ -437,41 +472,118 @@ class CoversModel(QAbstractListModel): # {{{ if role == Qt.UserRole: return waiting return NONE + + def plugin_for_index(self, index): + row = index.row() if hasattr(index, 'row') else index + for k, v in self.plugin_map.iteritems(): + if v == row: + return k + + def clear_failed(self): + good = [] + pmap = {} + for i, x in enumerate(self.covers): + if not x[-1]: + good.append(x) + if i > 0: + plugin = self.plugin_for_index(i) + pmap[plugin] = len(good) - 1 + good = [x for x in self.covers if not x[-1]] + self.covers = good + self.plugin_map = pmap + self.reset() + + def index_for_plugin(self, plugin): + idx = self.plugin_map.get(plugin, 0) + return self.index(idx) + + def update_result(self, plugin, width, height, data): + try: + idx = self.plugin_map[plugin] + except: + return + pmap = QPixmap() + pmap.loadFromData(data) + if pmap.isNull(): + return + self.covers[idx] = self.get_item(plugin.name, pmap, waiting=False) + self.dataChanged.emit(self.index(idx), self.index(idx)) + + def cover_pmap(self, index): + row = index.row() + if row > 0 and row < len(self.covers): + pmap = self.books[row][2] + if pmap is not None and not pmap.isNull(): + return pmap + # }}} -class CoverDelegate(QStyledItemDelegate): +class CoverDelegate(QStyledItemDelegate): # {{{ needs_redraw = pyqtSignal() - def __init__(self, parent=None): + def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) - self.movie = QMovie(I('spinner.gif')) - self.movie.frameChanged.connect(self.frame_changed) + self.angle = 0 + self.timer = QTimer(self) + self.timer.timeout.connect(self.frame_changed) + self.color = parent.palette().color(QPalette.WindowText) + self.spinner_width = 64 def frame_changed(self, *args): + self.angle = (self.angle+30)%360 self.needs_redraw.emit() - def start_movie(self): - self.movie.start() + def start_animation(self): + self.angle = 0 + self.timer.start(200) - def stop_movie(self): - self.movie.stop() + def stop_animation(self): + self.timer.stop() + + def draw_spinner(self, painter, rect): + width = rect.width() + + outer_radius = (width-1)*0.5 + inner_radius = (width-1)*0.5*0.38 + + capsule_height = outer_radius - inner_radius + capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35)) + capsule_radius = capsule_width//2 + + painter.save() + painter.setRenderHint(painter.Antialiasing) + + for i in xrange(12): + color = QColor(self.color) + color.setAlphaF(1.0 - (i/12.0)) + painter.setPen(Qt.NoPen) + painter.setBrush(color) + painter.save() + painter.translate(rect.center()) + painter.rotate(self.angle - i*30.0) + painter.drawRoundedRect(-capsule_width*0.5, + -(inner_radius+capsule_height), capsule_width, + capsule_height, capsule_radius, capsule_radius) + painter.restore() + painter.restore() def paint(self, painter, option, index): - waiting = index.data(Qt.UserRole).toBool() - if waiting: - pixmap = self.movie.currentPixmap() - prect = pixmap.rect() - prect.moveCenter(option.rect.center()) - painter.drawPixmap(prect, pixmap, pixmap.rect()) QStyledItemDelegate.paint(self, painter, option, index) + if self.timer.isActive() and index.data(Qt.UserRole).toBool(): + rect = QRect(0, 0, self.spinner_width, self.spinner_width) + rect.moveCenter(option.rect.center()) + self.draw_spinner(painter, rect) +# }}} class CoversView(QListView): # {{{ - def __init__(self, log, current_cover, parent=None): + chosen = pyqtSignal() + + def __init__(self, current_cover, parent=None): QListView.__init__(self, parent) - self.m = CoversModel(log, current_cover, self) + self.m = CoversModel(current_cover, self) self.setModel(self.m) self.setFlow(self.LeftToRight) @@ -487,6 +599,8 @@ class CoversView(QListView): # {{{ self.delegate.needs_redraw.connect(self.viewport().update, type=Qt.QueuedConnection) + self.doubleClicked.connect(self.chosen, type=Qt.QueuedConnection) + def select(self, num): current = self.model().index(num) sm = self.selectionModel() @@ -494,15 +608,24 @@ class CoversView(QListView): # {{{ def start(self): self.select(0) - self.delegate.start_movie() + self.delegate.start_animation() + + def clear_failed(self): + plugin = self.m.plugin_for_index(self.currentIndex()) + self.m.clear_failed() + self.select(self.m.index_for_plugin(plugin).row()) # }}} -class CoverWidget(QWidget): # {{{ +class CoversWidget(QWidget): # {{{ + + chosen = pyqtSignal() + finished = pyqtSignal() def __init__(self, log, current_cover, parent=None): QWidget.__init__(self, parent) self.log = log + self.abort = Event() self.l = l = QGridLayout() self.setLayout(l) @@ -511,8 +634,10 @@ class CoverWidget(QWidget): # {{{ self.msg.setWordWrap(True) l.addWidget(self.msg, 0, 0) - self.covers_view = CoversView(log, current_cover, self) + self.covers_view = CoversView(current_cover, self) + self.covers_view.chosen.connect(self.chosen) l.addWidget(self.covers_view, 1, 0) + self.continue_processing = True def start(self, book, current_cover, title, authors): self.book, self.current_cover = book, current_cover @@ -521,6 +646,66 @@ class CoverWidget(QWidget): # {{{ self.msg.setText('

'+_('Downloading covers for %s, please wait...')%book.title) self.covers_view.start() + self.worker = CoverWorker(self.log, self.abort, self.title, + self.authors, book.identifiers) + self.worker.start() + QTimer.singleShot(50, self.check) + self.covers_view.setFocus(Qt.OtherFocusReason) + + def check(self): + if self.worker.is_alive() and not self.abort.is_set(): + QTimer.singleShot(50, self.check) + try: + self.process_result(self.worker.rq.get_nowait()) + except Empty: + pass + else: + self.process_results() + + def process_results(self): + while self.continue_processing: + try: + self.process_result(self.worker.rq.get_nowait()) + except Empty: + break + + self.covers_view.clear_failed() + + if self.worker.error is not None: + error_dialog(self, _('Download failed'), + _('Failed to download any covers, click' + ' "Show details" for details.'), + det_msg=self.worker.error, show=True) + + num = self.covers_view.model().rowCount() + if num < 2: + txt = _('Could not find any covers for %s')%self.book.title + else: + txt = _('Found %d covers of %s. Pick the one you like' + ' best.')%(num, self.title) + self.msg.setText(txt) + + self.finished.emit() + + def process_result(self, result): + if not self.continue_processing: + return + plugin, width, height, fmt, data = result + self.covers_view.model().update_result(plugin, width, height, data) + + def cleanup(self): + self.covers_view.delegate.stop_animation() + self.continue_processing = False + + def cancel(self): + self.continue_processing = False + self.abort.set() + + @property + def cover_pmap(self): + return self.covers_view.model().cover_pmap( + self.covers_view.currentIndex()) + # }}} class FullFetch(QDialog): # {{{ @@ -528,6 +713,7 @@ class FullFetch(QDialog): # {{{ def __init__(self, log, current_cover=None, parent=None): QDialog.__init__(self, parent) self.log, self.current_cover = log, current_cover + self.book = self.cover_pmap = None self.setWindowTitle(_('Downloading metadata...')) self.setWindowIcon(QIcon(I('metadata.png'))) @@ -554,17 +740,20 @@ class FullFetch(QDialog): # {{{ self.identify_widget.book_selected.connect(self.book_selected) self.stack.addWidget(self.identify_widget) - self.cover_widget = CoverWidget(self.log, self.current_cover, parent=self) - self.stack.addWidget(self.cover_widget) + self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self) + self.covers_widget.chosen.connect(self.ok_clicked) + self.stack.addWidget(self.covers_widget) self.resize(850, 550) + self.finished.connect(self.cleanup) + def book_selected(self, book): self.next_button.setVisible(False) self.ok_button.setVisible(True) self.book = book self.stack.setCurrentIndex(1) - self.cover_widget.start(book, self.current_cover, + self.covers_widget.start(book, self.current_cover, self.title, self.authors) def accept(self): @@ -575,6 +764,9 @@ class FullFetch(QDialog): # {{{ self.identify_widget.cancel() return QDialog.reject(self) + def cleanup(self): + self.covers_widget.cleanup() + def identify_results_found(self): self.next_button.setEnabled(True) @@ -582,7 +774,8 @@ class FullFetch(QDialog): # {{{ self.identify_widget.get_result() def ok_clicked(self, *args): - pass + self.cover_pmap = self.covers_widget.cover_pmap + QDialog.accept(self) def start(self, title=None, authors=None, identifiers={}): self.title, self.authors = title, authors @@ -592,6 +785,7 @@ class FullFetch(QDialog): # {{{ # }}} if __name__ == '__main__': + DEVELOP_DIALOG = True app = QApplication([]) d = FullFetch(Log()) d.start(title='great gatsby', authors=['Fitzgerald'])