From 2577a155c11e0a688f8fbf63c85b9c7a87059391 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 9 Apr 2011 10:04:00 -0400 Subject: [PATCH 01/19] Apply styling to child toolbar. --- src/calibre/gui2/layout.py | 52 +++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 8a6ef3afe4..54f97d1039 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -308,22 +308,44 @@ class MenuBar(QMenuBar): # {{{ ac.setMenu(m) return ac - - # }}} -class ToolBar(QToolBar): # {{{ +class BaseToolBar(QToolBar): # {{{ - def __init__(self, donate, location_manager, child_bar, parent): + def __init__(self, parent): QToolBar.__init__(self, parent) - self.gui = parent - self.child_bar = child_bar self.setContextMenuPolicy(Qt.PreventContextMenu) self.setMovable(False) self.setFloatable(False) self.setOrientation(Qt.Horizontal) self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) self.setStyleSheet('QToolButton:checked { font-weight: bold }') + self.preferred_width = self.sizeHint().width() + + def resizeEvent(self, ev): + QToolBar.resizeEvent(self, ev) + style = self.get_text_style() + self.setToolButtonStyle(style) + + def get_text_style(self): + style = Qt.ToolButtonTextUnderIcon + s = gprefs['toolbar_icon_size'] + if s != 'off': + p = gprefs['toolbar_text'] + if p == 'never': + style = Qt.ToolButtonIconOnly + elif p == 'auto' and self.preferred_width > self.width()+35: + style = Qt.ToolButtonIconOnly + return style + +# }}} + +class ToolBar(BaseToolBar): # {{{ + + def __init__(self, donate, location_manager, child_bar, parent): + BaseToolBar.__init__(self, parent) + self.gui = parent + self.child_bar = child_bar self.donate_button = donate self.apply_settings() @@ -333,7 +355,6 @@ class ToolBar(QToolBar): # {{{ donate.setCursor(Qt.PointingHandCursor) self.added_actions = [] self.build_bar() - self.preferred_width = self.sizeHint().width() self.setAcceptDrops(True) def apply_settings(self): @@ -404,21 +425,6 @@ class ToolBar(QToolBar): # {{{ ch.setPopupMode(menu_mode) return ch - def resizeEvent(self, ev): - QToolBar.resizeEvent(self, ev) - style = Qt.ToolButtonTextUnderIcon - s = gprefs['toolbar_icon_size'] - if s != 'off': - p = gprefs['toolbar_text'] - if p == 'never': - style = Qt.ToolButtonIconOnly - - if p == 'auto' and self.preferred_width > self.width()+35 and \ - not gprefs['action-layout-toolbar-child']: - style = Qt.ToolButtonIconOnly - - self.setToolButtonStyle(style) - def database_changed(self, db): pass @@ -496,7 +502,7 @@ class MainWindowMixin(object): # {{{ self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) - self.child_bar = QToolBar(self) + self.child_bar = BaseToolBar(self) self.tool_bar = ToolBar(self.donate_button, self.location_manager, self.child_bar, self) self.addToolBar(Qt.TopToolBarArea, self.tool_bar) From 173a7dfc317337bd9751007c0524bb1cd507bc59 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 08:37:56 -0600 Subject: [PATCH 02/19] Fix #755444 (Connect/share icon to show content server running) --- ...share_on.png (PNG Image, 127x126 pixels).png | Bin 0 -> 11617 bytes src/calibre/gui2/actions/device.py | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 resources/images/connect_share_on.png (PNG Image, 127x126 pixels).png diff --git a/resources/images/connect_share_on.png (PNG Image, 127x126 pixels).png b/resources/images/connect_share_on.png (PNG Image, 127x126 pixels).png new file mode 100644 index 0000000000000000000000000000000000000000..3d431be1a1eebaac758bf5c9c3df4bc5e3dadf7f GIT binary patch literal 11617 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 literal 0 HcmV?d00001 diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 64bc4e69d7..debcbb6c1a 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -165,6 +165,10 @@ class ConnectShareAction(InterfaceAction): def content_server_state_changed(self, running): self.share_conn_menu.server_state_changed(running) + if running: + self.qaction.setIcon(QIcon(I('connect_share_on.png'))) + else: + self.qaction.setIcon(QIcon(I('connect_share.png'))) def toggle_content_server(self): if self.gui.content_server is None: From 745db1feb0d9212b70e780aad997c459ebb9203c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 08:55:04 -0600 Subject: [PATCH 03/19] Dvhn by Reijndert --- recipes/dvhn.recipe | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 recipes/dvhn.recipe diff --git a/recipes/dvhn.recipe b/recipes/dvhn.recipe new file mode 100644 index 0000000000..4c093aa9d2 --- /dev/null +++ b/recipes/dvhn.recipe @@ -0,0 +1,32 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1302341394(BasicNewsRecipe): + title = u'DvhN' + oldest_article = 1 + max_articles_per_feed = 200 + + __author__ = 'Reijndert' + no_stylesheets = True + cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif' + language = 'nl' + country = 'NL' + version = 1 + publisher = u'Dagblad van het Noorden' + category = u'Nieuws' + description = u'Nieuws uit Noord Nederland' + + + keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'}) + ,dict(name='div', attrs={'id':'articleText'}) + ] + + remove_tags = [ + dict(name=['object','link','iframe','base']) + ,dict(name='span',attrs={'class':'copyright'}) + ] + + feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')] + + extra_css = ''' + body {font-family: verdana, arial, helvetica, geneva, sans-serif;} + ''' From 394cdedce3889b88c258fcb6ad477fb3a66bf2a2 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 9 Apr 2011 11:46:08 -0400 Subject: [PATCH 04/19] Calculate preferred with with a built toolbar --- src/calibre/gui2/layout.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 54f97d1039..092f971862 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -320,7 +320,6 @@ class BaseToolBar(QToolBar): # {{{ self.setOrientation(Qt.Horizontal) self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) self.setStyleSheet('QToolButton:checked { font-weight: bold }') - self.preferred_width = self.sizeHint().width() def resizeEvent(self, ev): QToolBar.resizeEvent(self, ev) @@ -334,7 +333,7 @@ class BaseToolBar(QToolBar): # {{{ p = gprefs['toolbar_text'] if p == 'never': style = Qt.ToolButtonIconOnly - elif p == 'auto' and self.preferred_width > self.width()+35: + elif p == 'auto' and self.sizeHint().width() > self.width()+35: style = Qt.ToolButtonIconOnly return style From b7b156b2484e95e3047795912bd28b6da9ca5467 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 10:09:55 -0600 Subject: [PATCH 05/19] ... --- ...ge, 127x126 pixels).png => connect_share_on.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/images/{connect_share_on.png (PNG Image, 127x126 pixels).png => connect_share_on.png} (100%) diff --git a/resources/images/connect_share_on.png (PNG Image, 127x126 pixels).png b/resources/images/connect_share_on.png similarity index 100% rename from resources/images/connect_share_on.png (PNG Image, 127x126 pixels).png rename to resources/images/connect_share_on.png From bc038e1b7a842cef16d92784b1de2b3238587e94 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 10:25:08 -0600 Subject: [PATCH 06/19] ... --- src/calibre/gui2/metadata/single_download.py | 76 +++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 7fa052844f..b6bd38a828 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -13,7 +13,8 @@ from operator import attrgetter 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) + QTimer, pyqtSignal, QAbstractTableModel, QVariant, QSize, QListView, + QPixmap, QAbstractListModel) from PyQt4.QtWebKit import QWebView from calibre.customize.ui import metadata_plugins @@ -398,16 +399,85 @@ class IdentifyWidget(QWidget): # {{{ self.abort.set() # }}} +class CoversModel(QAbstractListModel): # {{{ + + def __init__(self, log, current_cover, parent=None): + QAbstractListModel.__init__(self, parent) + + 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.log = log + + def get_item(self, src, pmap): + 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) + + def rowCount(self, parent=None): + return len(self.covers) + + def data(self, index, role): + try: + text, pmap = self.covers[index.row()][:2] + except: + return None + if role == Qt.DecorationRole: + return pmap + if role == Qt.DisplayRole: + return text + return NONE +# }}} + +class CoversView(QListView): # {{{ + + def __init__(self, log, current_cover, parent=None): + QListView.__init__(self, parent) + self.m = CoversModel(log, current_cover, self) + self.setModel(self.m) + + self.setFlow(self.LeftToRight) + self.setWrapping(True) + self.setResizeMode(self.Adjust) + self.setGridSize(QSize(190, 260)) + self.setIconSize(QSize(150, 200)) + self.setSelectionMode(self.SingleSelection) + self.setViewMode(self.IconMode) + + def select(self, num): + current = self.model().index(num) + sm = self.selectionModel() + sm.select(current, sm.SelectCurrent) + +# }}} + class CoverWidget(QWidget): # {{{ - def __init__(self, log, parent=None): + def __init__(self, log, current_cover, parent=None): QWidget.__init__(self, parent) self.log = log + self.l = l = QGridLayout() + self.setLayout(l) + + self.msg = QLabel() + self.msg.setWordWrap(True) + l.addWidget(self.msg, 0, 0) + + self.covers_view = CoversView(log, current_cover, self) + l.addWidget(self.covers_view, 1, 0) + def start(self, book, current_cover, title, authors): 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) # }}} class FullFetch(QDialog): # {{{ @@ -441,7 +511,7 @@ class FullFetch(QDialog): # {{{ self.identify_widget.book_selected.connect(self.book_selected) self.stack.addWidget(self.identify_widget) - self.cover_widget = CoverWidget(self.log, parent=self) + self.cover_widget = CoverWidget(self.log, self.current_cover, parent=self) self.stack.addWidget(self.cover_widget) self.resize(850, 500) From f8d4e6bfa055a35a8dfd3eb742c16246d97b6b8c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 10:40:56 -0600 Subject: [PATCH 07/19] Fix segfault caused by calling sizeHint from within a resize event handler --- src/calibre/gui2/layout.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index c90c1312ae..e98817a02f 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -320,6 +320,7 @@ class BaseToolBar(QToolBar): # {{{ self.setOrientation(Qt.Horizontal) self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) self.setStyleSheet('QToolButton:checked { font-weight: bold }') + self.preferred_width = self.sizeHint().width() def resizeEvent(self, ev): QToolBar.resizeEvent(self, ev) @@ -333,7 +334,7 @@ class BaseToolBar(QToolBar): # {{{ p = gprefs['toolbar_text'] if p == 'never': style = Qt.ToolButtonIconOnly - elif p == 'auto' and self.sizeHint().width() > self.width()+35: + elif p == 'auto' and self.preferred_width > self.width()+35: style = Qt.ToolButtonIconOnly return style @@ -414,6 +415,8 @@ class ToolBar(BaseToolBar): # {{{ bar.addAction(action.qaction) self.added_actions.append(action.qaction) self.setup_tool_button(bar, action.qaction, action.popup_type) + self.preferred_width = self.sizeHint().width() + self.child_bar.preferred_width = self.child_bar.sizeHint().width() def setup_tool_button(self, bar, ac, menu_mode=None): ch = bar.widgetForAction(ac) From b231742a012a1b5a1e9ba30b4564a482b2d85018 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 9 Apr 2011 11:35:39 -0600 Subject: [PATCH 08/19] Animated list views, what fun --- resources/images/spinner.gif | Bin 0 -> 10848 bytes src/calibre/ebooks/metadata/sources/amazon.py | 2 +- src/calibre/gui2/metadata/single_download.py | 65 +++++++++++++++--- src/calibre/utils/config.py | 3 +- 4 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 resources/images/spinner.gif diff --git a/resources/images/spinner.gif b/resources/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..5e864f2e37d51e2b45731822225161780027ea2e GIT binary patch literal 10848 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 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 09/19] 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 10/19] 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 11/19] ... --- 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 12/19] ... --- 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 13/19] 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 14/19] 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')+ '
.

'+ '''