aJ|lTA_rrWF5uT
zY4~wJ$vB&(2#|}5N)u-%Ze$qaA9Oo=%*qP>K*SsCHH^+(-Low~)%Z`%BM0IcrTFwq
ze2*cNsedGydG5{DfJCmPRi|vxnLGXHEw7Ty3(kJ>Pc;3YL!>ykSU<0{oQ%IeZQ>##
ww?>6WhB0PLXE`z~*IF?`BR6ekhpyQa5)v~#bb~6<-hmOhaZUKT7{r|a0zgga9RL6T
literal 0
HcmV?d00001
diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py
index b070132de9..4722873f77 100644
--- a/src/calibre/ebooks/metadata/sources/amazon.py
+++ b/src/calibre/ebooks/metadata/sources/amazon.py
@@ -279,7 +279,7 @@ class Worker(Thread): # Get details {{{
class Amazon(Source):
- name = 'Amazon Store'
+ name = 'Amazon Web'
description = _('Downloads metadata from Amazon')
capabilities = frozenset(['identify', 'cover'])
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index b6bd38a828..2c9b234fba 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -14,7 +14,7 @@ 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)
+ QPixmap, QAbstractListModel, QMovie)
from PyQt4.QtWebKit import QWebView
from calibre.customize.ui import metadata_plugins
@@ -407,33 +407,66 @@ class CoversModel(QAbstractListModel): # {{{
if current_cover is None:
current_cover = QPixmap(I('default_cover.png'))
- self.covers = [self.get_item(_('Current cover'), current_cover)]
- for i in range(10):
- self.covers.append(self.covers[0])
+ 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.append((plugin.name+'\n'+_('Searching...'),
+ QVariant(self.blank), None, True))
self.log = log
- def get_item(self, src, pmap):
+ def get_item(self, src, pmap, waiting=True):
sz = '%dx%d'%(pmap.width(), pmap.height())
text = QVariant(src + '\n' + sz)
scaled = pmap.scaled(150, 200, Qt.IgnoreAspectRatio,
Qt.SmoothTransformation)
- return (text, QVariant(scaled), pmap)
+ return (text, QVariant(scaled), pmap, waiting)
def rowCount(self, parent=None):
return len(self.covers)
def data(self, index, role):
try:
- text, pmap = self.covers[index.row()][:2]
+ text, pmap, cover, waiting = self.covers[index.row()]
except:
- return None
+ return NONE
if role == Qt.DecorationRole:
return pmap
if role == Qt.DisplayRole:
return text
+ if role == Qt.UserRole:
+ return waiting
return NONE
# }}}
+class CoverDelegate(QStyledItemDelegate):
+
+ needs_redraw = pyqtSignal()
+
+ def __init__(self, parent=None):
+ QStyledItemDelegate.__init__(self, parent)
+
+ self.movie = QMovie(I('spinner.gif'))
+ self.movie.frameChanged.connect(self.frame_changed)
+
+ def frame_changed(self, *args):
+ self.needs_redraw.emit()
+
+ def start_movie(self):
+ self.movie.start()
+
+ def stop_movie(self):
+ self.movie.stop()
+
+ 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)
+
class CoversView(QListView): # {{{
def __init__(self, log, current_cover, parent=None):
@@ -449,11 +482,20 @@ class CoversView(QListView): # {{{
self.setSelectionMode(self.SingleSelection)
self.setViewMode(self.IconMode)
+ self.delegate = CoverDelegate(self)
+ self.setItemDelegate(self.delegate)
+ self.delegate.needs_redraw.connect(self.viewport().update,
+ type=Qt.QueuedConnection)
+
def select(self, num):
current = self.model().index(num)
sm = self.selectionModel()
sm.select(current, sm.SelectCurrent)
+ def start(self):
+ self.select(0)
+ self.delegate.start_movie()
+
# }}}
class CoverWidget(QWidget): # {{{
@@ -476,8 +518,9 @@ class CoverWidget(QWidget): # {{{
self.book, self.current_cover = book, current_cover
self.title, self.authors = title, authors
self.log('\n\nStarting cover download for:', book.title)
- self.msg.setText(_('Downloading covers for %s, please wait...')%book.title)
- self.covers_view.select(0)
+ self.msg.setText(''+_('Downloading covers for %s, please wait...')%book.title)
+ self.covers_view.start()
+
# }}}
class FullFetch(QDialog): # {{{
@@ -514,7 +557,7 @@ class FullFetch(QDialog): # {{{
self.cover_widget = CoverWidget(self.log, self.current_cover, parent=self)
self.stack.addWidget(self.cover_widget)
- self.resize(850, 500)
+ self.resize(850, 550)
def book_selected(self, book):
self.next_button.setVisible(False)
diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py
index 078971428d..7f2c75a272 100644
--- a/src/calibre/utils/config.py
+++ b/src/calibre/utils/config.py
@@ -786,8 +786,7 @@ def write_tweaks(raw):
tweaks = read_tweaks()
test_eight_code = tweaks.get('test_eight_code', False)
# test_eight_code notes
-# Change documentation of bool columns are tristate to indicate that it can be
-# overridden on a per library basis via Preferences->Custom columns
+# Change Amazon plugin name to just Amazon
def migrate():
if hasattr(os, 'geteuid') and os.geteuid() == 0:
From 3de4c6f7ca847d1e162c0e4a4eafb7491750affd Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 12:43:18 -0600
Subject: [PATCH 11/42] New usb ids for the entourage edge
---
src/calibre/devices/edge/driver.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/calibre/devices/edge/driver.py b/src/calibre/devices/edge/driver.py
index d14763f313..9491b9bc68 100644
--- a/src/calibre/devices/edge/driver.py
+++ b/src/calibre/devices/edge/driver.py
@@ -26,9 +26,9 @@ class EDGE(USBMS):
PRODUCT_ID = [0x0c02]
BCD = [0x0223]
- VENDOR_NAME = 'ANDROID'
- WINDOWS_MAIN_MEM = '__FILE-STOR_GADG'
- WINDOWS_CARD_A_MEM = '__FILE-STOR_GADG'
+ VENDOR_NAME = ['ANDROID', 'LINUX']
+ WINDOWS_MAIN_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET']
+ WINDOWS_CARD_A_MEM = ['__FILE-STOR_GADG', 'FILE-CD_GADGET']
MAIN_MEMORY_VOLUME_LABEL = 'Edge Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Edge Storage Card'
From d2fe0bf7ee71c8b4660f36aa01cf4113b8a7e33c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 15:41:24 -0600
Subject: [PATCH 12/42] 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'])
From 6bf57f68a58a58f03570d354936a09f73882071a Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 16:18:27 -0600
Subject: [PATCH 13/42] ...
---
src/calibre/gui2/metadata/single_download.py | 27 +++++++++++++-------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index c397a8660a..d725229344 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -417,8 +417,11 @@ class CoverWorker(Thread): # {{{
self.error = None
def fake_run(self):
+ images = ['donate.png', 'config.png', 'column.png', 'eject.png', ]
import time
time.sleep(2)
+ for pl, im in zip(metadata_plugins(['cover']), images):
+ self.rq.put((pl, 1, 1, 'png', I(im, data=True)))
def run(self):
try:
@@ -509,10 +512,10 @@ class CoversModel(QAbstractListModel): # {{{
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):
+ def cover_pixmap(self, index):
row = index.row()
if row > 0 and row < len(self.covers):
- pmap = self.books[row][2]
+ pmap = self.covers[row][2]
if pmap is not None and not pmap.isNull():
return pmap
@@ -682,7 +685,7 @@ class CoversWidget(QWidget): # {{{
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)
+ ' best.')%(num-1, self.title)
self.msg.setText(txt)
self.finished.emit()
@@ -701,9 +704,8 @@ class CoversWidget(QWidget): # {{{
self.continue_processing = False
self.abort.set()
- @property
- def cover_pmap(self):
- return self.covers_view.model().cover_pmap(
+ def cover_pixmap(self):
+ return self.covers_view.model().cover_pixmap(
self.covers_view.currentIndex())
# }}}
@@ -713,7 +715,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.book = self.cover_pixmap = None
self.setWindowTitle(_('Downloading metadata...'))
self.setWindowIcon(QIcon(I('metadata.png')))
@@ -774,8 +776,15 @@ class FullFetch(QDialog): # {{{
self.identify_widget.get_result()
def ok_clicked(self, *args):
- self.cover_pmap = self.covers_widget.cover_pmap
- QDialog.accept(self)
+ self.cover_pixmap = self.covers_widget.cover_pixmap()
+ if DEVELOP_DIALOG:
+ if self.cover_pixmap is not None:
+ self.w = QLabel()
+ self.w.setPixmap(self.cover_pixmap)
+ self.stack.addWidget(self.w)
+ self.stack.setCurrentIndex(2)
+ else:
+ QDialog.accept(self)
def start(self, title=None, authors=None, identifiers={}):
self.title, self.authors = title, authors
From d042ae6b7454801c0fcc6d64147de21d3ad4342f Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 16:22:04 -0600
Subject: [PATCH 14/42] ...
---
src/calibre/gui2/metadata/single_download.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index d725229344..83386445c5 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -705,8 +705,15 @@ class CoversWidget(QWidget): # {{{
self.abort.set()
def cover_pixmap(self):
- return self.covers_view.model().cover_pixmap(
- self.covers_view.currentIndex())
+ idx = None
+ for i in self.covers_view.selectionModel().selectedIndexes():
+ if i.isValid():
+ idx = i
+ break
+ if idx is None:
+ idx = self.covers_view.currentIndex()
+ return self.covers_view.model().cover_pixmap(idx)
+
# }}}
From eaf6060421f66bef2f1c7aefcad0ec1321f88eb6 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 16:53:10 -0600
Subject: [PATCH 15/42] Log viewer
---
src/calibre/gui2/metadata/single_download.py | 179 ++++++++++++-------
1 file changed, 113 insertions(+), 66 deletions(-)
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index 83386445c5..35c66340c6 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -15,7 +15,7 @@ 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, QColor, QRect)
+ QPixmap, QAbstractListModel, QColor, QRect, QTextBrowser)
from PyQt4.QtWebKit import QWebView
from calibre.customize.ui import metadata_plugins
@@ -28,7 +28,7 @@ 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
+DEBUG_DIALOG = False
class RichTextDelegate(QStyledItemDelegate): # {{{
@@ -57,6 +57,65 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
painter.restore()
# }}}
+class CoverDelegate(QStyledItemDelegate): # {{{
+
+ needs_redraw = pyqtSignal()
+
+ def __init__(self, parent):
+ QStyledItemDelegate.__init__(self, parent)
+
+ 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_animation(self):
+ self.angle = 0
+ self.timer.start(200)
+
+ 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):
+ 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 ResultsModel(QAbstractTableModel): # {{{
COLUMNS = (
@@ -273,7 +332,7 @@ class IdentifyWorker(Thread): # {{{
def run(self):
try:
- if DEVELOP_DIALOG:
+ if DEBUG_DIALOG:
self.results = self.sample_results()
else:
self.results = identify(self.log, self.abort, title=self.title,
@@ -425,7 +484,7 @@ class CoverWorker(Thread): # {{{
def run(self):
try:
- if DEVELOP_DIALOG:
+ if DEBUG_DIALOG:
self.fake_run()
else:
from calibre.ebooks.metadata.sources.covers import run_download
@@ -521,65 +580,6 @@ class CoversModel(QAbstractListModel): # {{{
# }}}
-class CoverDelegate(QStyledItemDelegate): # {{{
-
- needs_redraw = pyqtSignal()
-
- def __init__(self, parent):
- QStyledItemDelegate.__init__(self, parent)
-
- 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_animation(self):
- self.angle = 0
- self.timer.start(200)
-
- 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):
- 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): # {{{
chosen = pyqtSignal()
@@ -714,6 +714,46 @@ class CoversWidget(QWidget): # {{{
idx = self.covers_view.currentIndex()
return self.covers_view.model().cover_pixmap(idx)
+# }}}
+
+class LogViewer(QDialog): # {{{
+
+ def __init__(self, log, parent=None):
+ QDialog.__init__(self, parent)
+ self.log = log
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+
+ self.tb = QTextBrowser(self)
+ l.addWidget(self.tb)
+
+ self.bb = QDialogButtonBox(QDialogButtonBox.Close)
+ l.addWidget(self.bb)
+ self.bb.rejected.connect(self.reject)
+ self.bb.accepted.connect(self.accept)
+
+ self.setWindowTitle(_('Download log'))
+ self.setWindowIcon(QIcon(I('debug.png')))
+ self.resize(QSize(800, 400))
+
+ self.keep_updating = True
+ self.last_html = None
+ self.finished.connect(self.stop)
+ QTimer.singleShot(1000, self.update_log)
+
+ self.show()
+
+ def stop(self, *args):
+ self.keep_updating = False
+
+ def update_log(self):
+ if not self.keep_updating:
+ return
+ html = self.log.html
+ if html != self.last_html:
+ self.last_html = html
+ self.tb.setHtml('%s
'%html)
+ QTimer.singleShot(1000, self.update_log)
# }}}
@@ -738,10 +778,14 @@ class FullFetch(QDialog): # {{{
self.next_button = self.bb.addButton(_('Next'), self.bb.AcceptRole)
self.next_button.setDefault(True)
self.next_button.setEnabled(False)
+ self.next_button.setIcon(QIcon(I('ok.png')))
self.next_button.clicked.connect(self.next_clicked)
self.ok_button = self.bb.button(self.bb.Ok)
- self.ok_button.setVisible(False)
self.ok_button.clicked.connect(self.ok_clicked)
+ self.log_button = self.bb.addButton(_('View log'), self.bb.ActionRole)
+ self.log_button.clicked.connect(self.view_log)
+ self.log_button.setIcon(QIcon(I('debug.png')))
+ self.ok_button.setVisible(False)
self.identify_widget = IdentifyWidget(log, self)
self.identify_widget.rejected.connect(self.reject)
@@ -757,6 +801,9 @@ class FullFetch(QDialog): # {{{
self.finished.connect(self.cleanup)
+ def view_log(self):
+ self._lv = LogViewer(self.log, self)
+
def book_selected(self, book):
self.next_button.setVisible(False)
self.ok_button.setVisible(True)
@@ -784,7 +831,7 @@ class FullFetch(QDialog): # {{{
def ok_clicked(self, *args):
self.cover_pixmap = self.covers_widget.cover_pixmap()
- if DEVELOP_DIALOG:
+ if DEBUG_DIALOG:
if self.cover_pixmap is not None:
self.w = QLabel()
self.w.setPixmap(self.cover_pixmap)
@@ -801,7 +848,7 @@ class FullFetch(QDialog): # {{{
# }}}
if __name__ == '__main__':
- DEVELOP_DIALOG = True
+ DEBUG_DIALOG = True
app = QApplication([])
d = FullFetch(Log())
d.start(title='great gatsby', authors=['Fitzgerald'])
From a3e8c2560b1d00a40defd763c017c5c05f73e441 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Sat, 9 Apr 2011 17:43:33 -0600
Subject: [PATCH 16/42] Identifiers to URLs
---
src/calibre/customize/ui.py | 9 ++++--
src/calibre/ebooks/metadata/sources/amazon.py | 8 +++++
src/calibre/ebooks/metadata/sources/base.py | 7 ++++
src/calibre/ebooks/metadata/sources/google.py | 6 ++++
.../ebooks/metadata/sources/identify.py | 14 +++++++-
src/calibre/gui2/metadata/single_download.py | 32 +++++++++++++++----
6 files changed, 66 insertions(+), 10 deletions(-)
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index 9c8f80544b..b8abbf9b03 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -453,12 +453,15 @@ def epub_fixers():
# Metadata sources2 {{{
def metadata_plugins(capabilities):
capabilities = frozenset(capabilities)
- for plugin in _initialized_plugins:
- if isinstance(plugin, Source) and \
- plugin.capabilities.intersection(capabilities) and \
+ for plugin in all_metadata_plugins():
+ if plugin.capabilities.intersection(capabilities) and \
not is_disabled(plugin):
yield plugin
+def all_metadata_plugins():
+ for plugin in _initialized_plugins:
+ if isinstance(plugin, Source):
+ yield plugin
# }}}
# Initialize plugins {{{
diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py
index 4722873f77..5262ee0d1d 100644
--- a/src/calibre/ebooks/metadata/sources/amazon.py
+++ b/src/calibre/ebooks/metadata/sources/amazon.py
@@ -295,6 +295,14 @@ class Amazon(Source):
'uk' : _('UK'),
}
+ def get_book_url(self, identifiers): # {{{
+ asin = identifiers.get('amazon', None)
+ if asin is None:
+ asin = identifiers.get('asin', None)
+ if asin:
+ return 'http://amzn.com/%s'%asin
+ # }}}
+
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
domain = self.prefs.get('domain', 'com')
diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py
index d4e090084c..7c7d51077b 100644
--- a/src/calibre/ebooks/metadata/sources/base.py
+++ b/src/calibre/ebooks/metadata/sources/base.py
@@ -301,6 +301,13 @@ class Source(Plugin):
# Metadata API {{{
+ def get_book_url(self, identifiers):
+ '''
+ Return the URL for the book identified by identifiers at this source.
+ If no URL is found, return None.
+ '''
+ return None
+
def get_cached_cover_url(self, identifiers):
'''
Return cached cover URL for the book identified by
diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py
index 47cfb823bb..c3286700f2 100644
--- a/src/calibre/ebooks/metadata/sources/google.py
+++ b/src/calibre/ebooks/metadata/sources/google.py
@@ -167,6 +167,12 @@ class GoogleBooks(Source):
GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1'
+ def get_book_url(self, identifiers): # {{{
+ goog = identifiers.get('google', None)
+ if goog is not None:
+ return 'http://books.google.com/books?id=%s'%goog
+ # }}}
+
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
isbn = check_isbn(identifiers.get('isbn', None))
diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py
index 85549904e7..6775de01a6 100644
--- a/src/calibre/ebooks/metadata/sources/identify.py
+++ b/src/calibre/ebooks/metadata/sources/identify.py
@@ -14,7 +14,7 @@ from threading import Thread
from io import BytesIO
from operator import attrgetter
-from calibre.customize.ui import metadata_plugins
+from calibre.customize.ui import metadata_plugins, all_metadata_plugins
from calibre.ebooks.metadata.sources.base import create_log, msprefs
from calibre.ebooks.metadata.xisbn import xisbn
from calibre.ebooks.metadata.book.base import Metadata
@@ -366,6 +366,18 @@ def identify(log, abort, # {{{
return results
# }}}
+def urls_from_identifiers(identifiers): # {{{
+ ans = []
+ for plugin in all_metadata_plugins():
+ try:
+ url = plugin.get_book_url(identifiers)
+ if url is not None:
+ ans.append((plugin.name, url))
+ except:
+ pass
+ return ans
+# }}}
+
if __name__ == '__main__': # tests {{{
# To run these test use: calibre-debug -e
# src/calibre/ebooks/metadata/sources/identify.py
diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py
index 35c66340c6..cae6ede7b4 100644
--- a/src/calibre/gui2/metadata/single_download.py
+++ b/src/calibre/gui2/metadata/single_download.py
@@ -7,6 +7,9 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+DEBUG_DIALOG = False
+
+# Imports {{{
from threading import Thread, Event
from operator import attrgetter
from Queue import Queue, Empty
@@ -21,14 +24,14 @@ from PyQt4.QtWebKit import QWebView
from calibre.customize.ui import metadata_plugins
from calibre.ebooks.metadata import authors_to_string
from calibre.utils.logging import GUILog as Log
-from calibre.ebooks.metadata.sources.identify import identify
+from calibre.ebooks.metadata.sources.identify import (identify,
+ urls_from_identifiers)
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
-
-DEBUG_DIALOG = False
+# }}}
class RichTextDelegate(QStyledItemDelegate): # {{{
@@ -41,7 +44,11 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
return doc
def sizeHint(self, option, index):
- ans = self.to_doc(index).size().toSize()
+ doc = self.to_doc(index)
+ ans = doc.size().toSize()
+ if ans.width() > 250:
+ doc.setTextWidth(250)
+ ans = doc.size().toSize()
ans.setHeight(ans.height()+10)
return ans
@@ -234,6 +241,11 @@ class ResultsView(QTableView): # {{{
if not book.is_null('rating'):
parts.append('%s
'%('\u2605'*int(book.rating)))
parts.append('')
+ if book.identifiers:
+ urls = urls_from_identifiers(book.identifiers)
+ ids = ['%s'%(url, name) for name, url in urls]
+ if ids:
+ parts.append('%s: %s
'%(_('See at'), ', '.join(ids)))
if book.tags:
parts.append('%s
\u00a0
'%', '.join(book.tags))
if book.comments:
@@ -265,6 +277,14 @@ class Comments(QWebView): # {{{
self.page().setPalette(palette)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
+ self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
+ self.linkClicked.connect(self.link_clicked)
+
+ def link_clicked(self, url):
+ from calibre.gui2 import open_url
+ if unicode(url.toString()).startswith('http://'):
+ open_url(url)
+
def turnoff_scrollbar(self, *args):
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
@@ -382,7 +402,7 @@ class IdentifyWidget(QWidget): # {{{
self.query.setWordWrap(True)
l.addWidget(self.query, 2, 0, 1, 2)
- self.comments_view.show_data(''+_('Downloading')+
+ self.comments_view.show_data(''+_('Please wait')+
'
.
'+
'''