diff --git a/recipes/brand_eins.recipe b/recipes/brand_eins.recipe index 9b77c7f279..15e1d3ccca 100644 --- a/recipes/brand_eins.recipe +++ b/recipes/brand_eins.recipe @@ -3,8 +3,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Constantin Hofstetter , Steffen Siebert ' -__version__ = '0.97' - +__version__ = '0.98' # 2011-04-10 ''' http://brandeins.de - Wirtschaftsmagazin ''' import re import string @@ -14,8 +13,8 @@ from calibre.web.feeds.recipes import BasicNewsRecipe class BrandEins(BasicNewsRecipe): title = u'brand eins' - __author__ = 'Constantin Hofstetter' - description = u'Wirtschaftsmagazin' + __author__ = 'Constantin Hofstetter; Steffen Siebert' + description = u'Wirtschaftsmagazin: Gets the last full issue on default. Set a integer value for the username-field to get older issues: 1 -> the newest (but not complete) issue, 2 -> the last complete issue (default), 3 -> the issue before 2 etc.' publisher ='brandeins.de' category = 'politics, business, wirtschaft, Germany' use_embedded_content = False 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;} + ''' diff --git a/resources/images/connect_share_on.png b/resources/images/connect_share_on.png new file mode 100644 index 0000000000..3d431be1a1 Binary files /dev/null and b/resources/images/connect_share_on.png differ 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/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' diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index b070132de9..5262ee0d1d 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']) @@ -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..67a80d5785 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -78,8 +78,8 @@ class InternalMetadataCompareKeyGen(object): exact_title = 1 if title and \ cleanup_title(title) == cleanup_title(mi.title) else 2 - has_cover = 2 if source_plugin.get_cached_cover_url(mi.identifiers)\ - is None else 1 + has_cover = 2 if (not source_plugin.cached_cover_url_is_reliable or + source_plugin.get_cached_cover_url(mi.identifiers) is None) else 1 self.base = (isbn, has_cover, all_fields, exact_title) self.comments_len = len(mi.comments.strip() if mi.comments else '') @@ -157,6 +157,12 @@ class Source(Plugin): #: correctly first supports_gzip_transfer_encoding = False + #: Cached cover URLs can sometimes be unreliable (i.e. the download could + #: fail or the returned image could be bogus. If that is the case set this to + #: False + cached_cover_url_is_reliable = True + + def __init__(self, *args, **kwargs): Plugin.__init__(self, *args, **kwargs) self._isbn_to_identifier_cache = {} @@ -301,6 +307,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..4133d4d527 100644 --- a/src/calibre/ebooks/metadata/sources/google.py +++ b/src/calibre/ebooks/metadata/sources/google.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import time +import time, hashlib from urllib import urlencode from functools import partial from Queue import Queue, Empty @@ -133,7 +133,7 @@ def to_metadata(browser, log, entry_, timeout): # {{{ default = utcnow().replace(day=15) mi.pubdate = parse_date(pubdate, assume_utc=True, default=default) except: - log.exception('Failed to parse pubdate') + log.error('Failed to parse pubdate %r'%pubdate) # Ratings for x in rating(extra): @@ -164,9 +164,18 @@ class GoogleBooks(Source): 'comments', 'publisher', 'identifier:isbn', 'rating', 'identifier:google']) # language currently disabled supports_gzip_transfer_encoding = True + cached_cover_url_is_reliable = False GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1' + DUMMY_IMAGE_MD5 = frozenset(['0de4383ebad0adad5eeb8975cd796657']) + + 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)) @@ -229,7 +238,11 @@ class GoogleBooks(Source): log('Downloading cover from:', cached_url) try: cdata = br.open_novisit(cached_url, timeout=timeout).read() - result_queue.put((self, cdata)) + if cdata: + if hashlib.md5(cdata).hexdigest() in self.DUMMY_IMAGE_MD5: + log.warning('Google returned a dummy image, ignoring') + else: + result_queue.put((self, cdata)) except: log.exception('Failed to download cover from:', cached_url) diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 85549904e7..cd658a0daf 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 @@ -338,8 +338,9 @@ def identify(log, abort, # {{{ for i, result in enumerate(presults): result.relevance_in_source = i - result.has_cached_cover_url = \ - plugin.get_cached_cover_url(result.identifiers) is not None + result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable + and plugin.get_cached_cover_url(result.identifiers) is not + None) result.identify_plugin = plugin log('The identify phase took %.2f seconds'%(time.time() - start_time)) @@ -366,6 +367,22 @@ 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 + isbn = identifiers.get('isbn', None) + if isbn: + ans.append(('ISBN', + 'http://www.worldcat.org/search?q=bn%%3A%s&qt=advanced'%isbn)) + 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/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: diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index e102c35fbe..e98817a02f 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -308,22 +308,47 @@ 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 + + def contextMenuEvent(self, *args): + pass + +# }}} + +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 +358,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): @@ -348,9 +372,6 @@ class ToolBar(QToolBar): # {{{ self.child_bar.setToolButtonStyle(style) self.donate_button.set_normal_icon_size(sz, sz) - def contextMenuEvent(self, *args): - pass - def build_bar(self): self.showing_donate = False showing_device = self.location_manager.has_device @@ -394,6 +415,8 @@ class ToolBar(QToolBar): # {{{ 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) @@ -405,21 +428,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 @@ -497,7 +505,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) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index b0b7115ca1..537736d3d0 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en' import textwrap, re, os -from PyQt4.Qt import (Qt, QDateEdit, QDate, +from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QIcon, QToolButton, QWidget, QLabel, QGridLayout, QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QPushButton, QSpinBox, QLineEdit, QSizePolicy) @@ -315,7 +315,7 @@ class SeriesEdit(MultiCompleteComboBox): if not val: val = '' self.setEditText(val.strip()) - self.setCursorPosition(0) + self.lineEdit().setCursorPosition(0) return property(fget=fget, fset=fset) @@ -613,6 +613,8 @@ class FormatsManager(QWidget): # {{{ class Cover(ImageView): # {{{ + download_cover = pyqtSignal() + def __init__(self, parent): ImageView.__init__(self, parent) self.dialog = parent @@ -703,9 +705,6 @@ class Cover(ImageView): # {{{ cdata = im.export('png') self.current_val = cdata - def download_cover(self, *args): - pass # TODO: Implement this - def generate_cover(self, *args): from calibre.ebooks import calibre_cover from calibre.ebooks.metadata import fmt_sidx @@ -862,6 +861,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ if not val: val = [] self.setText(', '.join([x.strip() for x in val])) + self.setCursorPosition(0) return property(fget=fget, fset=fset) def initialize(self, db, id_): @@ -928,6 +928,7 @@ class IdentifiersEdit(QLineEdit): # {{{ val = {} txt = ', '.join(['%s:%s'%(k, v) for k, v in val.iteritems()]) self.setText(txt.strip()) + self.setCursorPosition(0) return property(fget=fget, fset=fset) def initialize(self, db, id_): @@ -977,7 +978,7 @@ class PublisherEdit(MultiCompleteComboBox): # {{{ if not val: val = '' self.setEditText(val.strip()) - self.setCursorPosition(0) + self.lineEdit().setCursorPosition(0) return property(fget=fget, fset=fset) diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 2ef183fca5..395f2aacab 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -16,11 +16,12 @@ from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QSizePolicy, QPalette, QFrame, QSize, QKeySequence) from calibre.ebooks.metadata import authors_to_string, string_to_authors -from calibre.gui2 import ResizableDialog, error_dialog, gprefs +from calibre.gui2 import ResizableDialog, error_dialog, gprefs, pixmap_to_data from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit, AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit, RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, BuddyLabel, DateEdit, PubdateEdit) +from calibre.gui2.metadata.single_download import FullFetch from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.utils.config import tweaks @@ -132,6 +133,7 @@ class MetadataSingleDialogBase(ResizableDialog): self.formats_manager.cover_from_format_button.clicked.connect( self.cover_from_format) self.cover = Cover(self) + self.cover.download_cover.connect(self.download_cover) self.basic_metadata_widgets.append(self.cover) self.comments = CommentsEdit(self, self.one_line_comments_toolbar) @@ -158,7 +160,7 @@ class MetadataSingleDialogBase(ResizableDialog): self.basic_metadata_widgets.extend([self.timestamp, self.pubdate]) self.fetch_metadata_button = QPushButton( - _('&Fetch metadata from server'), self) + _('&Download metadata'), self) self.fetch_metadata_button.clicked.connect(self.fetch_metadata) font = self.fmb_font = QFont() font.setBold(True) @@ -303,7 +305,26 @@ class MetadataSingleDialogBase(ResizableDialog): self.comments.current_val = mi.comments def fetch_metadata(self, *args): - pass # TODO: fetch metadata + d = FullFetch(self.cover.pixmap(), self) + ret = d.start(title=self.title.current_val, authors=self.authors.current_val, + identifiers=self.identifiers.current_val) + if ret == d.Accepted: + mi = d.book + if mi is not None: + self.update_from_mi(mi) + if d.cover_pixmap is not None: + self.cover.current_val = pixmap_to_data(d.cover_pixmap) + + def download_cover(self, *args): + from calibre.gui2.metadata.single_download import CoverFetch + d = CoverFetch(self.cover.pixmap(), self) + ret = d.start(self.title.current_val, self.authors.current_val, + self.identifiers.current_val) + if ret == d.Accepted: + if d.cover_pixmap is not None: + self.cover.current_val = pixmap_to_data(d.cover_pixmap) + + # }}} def apply_changes(self): diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 7fa052844f..8f01c6df1e 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -7,23 +7,31 @@ __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 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, QColor, QRect, QTextBrowser) 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 +# }}} class RichTextDelegate(QStyledItemDelegate): # {{{ @@ -36,7 +44,10 @@ 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() > 150: + ans.setWidth(160) ans.setHeight(ans.height()+10) return ans @@ -52,6 +63,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 = ( @@ -110,6 +180,13 @@ class ResultsModel(QAbstractTableModel): # {{{ return self.yes_icon elif role == Qt.UserRole: return book + elif role == Qt.ToolTipRole and col == 3: + return QVariant( + _('The has cover indication is not fully\n' + 'reliable. Sometimes results marked as not\n' + 'having a cover will find a cover in the download\n' + 'cover stage, and vice versa.')) + return NONE def sort(self, col, order=Qt.AscendingOrder): @@ -119,7 +196,7 @@ class ResultsModel(QAbstractTableModel): # {{{ elif col == 1: key = attrgetter('title') elif col == 2: - key = attrgetter('authors') + key = attrgetter('pubdate') elif col == 3: key = attrgetter('has_cached_cover_url') elif key == 4: @@ -170,6 +247,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: @@ -201,6 +283,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) @@ -268,7 +358,7 @@ class IdentifyWorker(Thread): # {{{ def run(self): try: - if True: + if DEBUG_DIALOG: self.results = self.sample_results() else: self.results = identify(self.log, self.abort, title=self.title, @@ -277,7 +367,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): # {{{ @@ -318,7 +408,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')+ '
.

'+ '''