mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
8127e5a4fe
@ -3,8 +3,7 @@
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Constantin Hofstetter <consti at consti.de>, Steffen Siebert <calibre at steffensiebert.de>'
|
||||
__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
|
||||
|
32
recipes/dvhn.recipe
Normal file
32
recipes/dvhn.recipe
Normal file
@ -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;}
|
||||
'''
|
BIN
resources/images/connect_share_on.png
Normal file
BIN
resources/images/connect_share_on.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -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 {{{
|
||||
|
@ -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'
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -7,23 +7,31 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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('<div>%s</div>'%('\u2605'*int(book.rating)))
|
||||
parts.append('</center>')
|
||||
if book.identifiers:
|
||||
urls = urls_from_identifiers(book.identifiers)
|
||||
ids = ['<a href="%s">%s</a>'%(url, name) for name, url in urls]
|
||||
if ids:
|
||||
parts.append('<div><b>%s:</b> %s</div><br>'%(_('See at'), ', '.join(ids)))
|
||||
if book.tags:
|
||||
parts.append('<div>%s</div><div>\u00a0</div>'%', '.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('<h2>'+_('Downloading')+
|
||||
self.comments_view.show_data('<h2>'+_('Please wait')+
|
||||
'<br><span id="dots">.</span></h2>'+
|
||||
'''
|
||||
<script type="text/javascript">
|
||||
@ -345,7 +435,7 @@ class IdentifyWidget(QWidget): # {{{
|
||||
if authors:
|
||||
parts.append('authors:'+authors_to_string(authors))
|
||||
if identifiers:
|
||||
x = ', '.join('%s:%s'%(k, v) for k, v in identifiers)
|
||||
x = ', '.join('%s:%s'%(k, v) for k, v in identifiers.iteritems())
|
||||
parts.append(x)
|
||||
self.query.setText(_('Query: ')+'; '.join(parts))
|
||||
self.log(unicode(self.query.text()))
|
||||
@ -398,23 +488,323 @@ class IdentifyWidget(QWidget): # {{{
|
||||
self.abort.set()
|
||||
# }}}
|
||||
|
||||
class CoverWidget(QWidget): # {{{
|
||||
class CoverWorker(Thread): # {{{
|
||||
|
||||
def __init__(self, log, parent=None):
|
||||
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):
|
||||
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:
|
||||
if DEBUG_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, current_cover, parent=None):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
|
||||
if current_cover is None:
|
||||
current_cover = QPixmap(I('default_cover.png'))
|
||||
|
||||
self.blank = QPixmap(I('blank.png')).scaled(150, 200)
|
||||
|
||||
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.plugin_map[plugin] = i+1
|
||||
|
||||
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,
|
||||
Qt.SmoothTransformation)
|
||||
return (text, QVariant(scaled), pmap, waiting)
|
||||
|
||||
def rowCount(self, parent=None):
|
||||
return len(self.covers)
|
||||
|
||||
def data(self, index, role):
|
||||
try:
|
||||
text, pmap, cover, waiting = self.covers[index.row()]
|
||||
except:
|
||||
return NONE
|
||||
if role == Qt.DecorationRole:
|
||||
return pmap
|
||||
if role == Qt.DisplayRole:
|
||||
return text
|
||||
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 cover_keygen(self, x):
|
||||
pmap = x[2]
|
||||
if pmap is None:
|
||||
return 1
|
||||
return pmap.width()*pmap.height()
|
||||
|
||||
|
||||
def clear_failed(self):
|
||||
good = []
|
||||
pmap = {}
|
||||
dcovers = sorted(self.covers[1:], key=self.cover_keygen, reverse=True)
|
||||
for i, x in enumerate(self.covers[0:1] + dcovers):
|
||||
if not x[-1]:
|
||||
good.append(x)
|
||||
if i > 0:
|
||||
plugin = self.plugin_for_index(i)
|
||||
pmap[plugin] = len(good) - 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_pixmap(self, index):
|
||||
row = index.row()
|
||||
if row > 0 and row < len(self.covers):
|
||||
pmap = self.covers[row][2]
|
||||
if pmap is not None and not pmap.isNull():
|
||||
return pmap
|
||||
|
||||
# }}}
|
||||
|
||||
class CoversView(QListView): # {{{
|
||||
|
||||
chosen = pyqtSignal()
|
||||
|
||||
def __init__(self, current_cover, parent=None):
|
||||
QListView.__init__(self, parent)
|
||||
self.m = CoversModel(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)
|
||||
|
||||
self.delegate = CoverDelegate(self)
|
||||
self.setItemDelegate(self.delegate)
|
||||
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()
|
||||
sm.select(current, sm.SelectCurrent)
|
||||
|
||||
def start(self):
|
||||
self.select(0)
|
||||
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 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)
|
||||
|
||||
self.msg = QLabel()
|
||||
self.msg.setWordWrap(True)
|
||||
l.addWidget(self.msg, 0, 0)
|
||||
|
||||
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
|
||||
self.title, self.authors = title, authors
|
||||
self.log('\n\nStarting cover download for:', book.title)
|
||||
self.log('Starting cover download for:', book.title)
|
||||
self.log('Query:', title, authors, self.book.identifiers)
|
||||
self.msg.setText('<p>'+_('Downloading covers for <b>%s</b>, 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 <b>%s</b>')%self.book.title
|
||||
else:
|
||||
txt = _('Found <b>%d</b> covers of %s. Pick the one you like'
|
||||
' best.')%(num-1, 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()
|
||||
|
||||
def cover_pixmap(self):
|
||||
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)
|
||||
|
||||
# }}}
|
||||
|
||||
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.copy_button = self.bb.addButton(_('Copy to clipboard'),
|
||||
self.bb.ActionRole)
|
||||
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
||||
self.copy_button.setIcon(QIcon(I('edit-copy.png')))
|
||||
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(100, self.update_log)
|
||||
|
||||
self.show()
|
||||
|
||||
def copy_to_clipboard(self):
|
||||
QApplication.clipboard().setText(''.join(self.log.plain_text))
|
||||
|
||||
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('<pre style="font-family:monospace">%s</pre>'%html)
|
||||
QTimer.singleShot(1000, self.update_log)
|
||||
|
||||
# }}}
|
||||
|
||||
class FullFetch(QDialog): # {{{
|
||||
|
||||
def __init__(self, log, current_cover=None, parent=None):
|
||||
def __init__(self, current_cover=None, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.log, self.current_cover = log, current_cover
|
||||
self.current_cover = current_cover
|
||||
self.log = Log()
|
||||
self.book = self.cover_pixmap = None
|
||||
|
||||
self.setWindowTitle(_('Downloading metadata...'))
|
||||
self.setWindowIcon(QIcon(I('metadata.png')))
|
||||
@ -430,28 +820,39 @@ 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 = IdentifyWidget(self.log, self)
|
||||
self.identify_widget.rejected.connect(self.reject)
|
||||
self.identify_widget.results_found.connect(self.identify_results_found)
|
||||
self.identify_widget.book_selected.connect(self.book_selected)
|
||||
self.stack.addWidget(self.identify_widget)
|
||||
|
||||
self.cover_widget = CoverWidget(self.log, 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, 500)
|
||||
self.resize(850, 550)
|
||||
|
||||
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)
|
||||
self.book = book
|
||||
self.stack.setCurrentIndex(1)
|
||||
self.cover_widget.start(book, self.current_cover,
|
||||
self.log('\n\n')
|
||||
self.covers_widget.start(book, self.current_cover,
|
||||
self.title, self.authors)
|
||||
|
||||
def accept(self):
|
||||
@ -460,8 +861,12 @@ class FullFetch(QDialog): # {{{
|
||||
|
||||
def reject(self):
|
||||
self.identify_widget.cancel()
|
||||
self.covers_widget.cancel()
|
||||
return QDialog.reject(self)
|
||||
|
||||
def cleanup(self):
|
||||
self.covers_widget.cleanup()
|
||||
|
||||
def identify_results_found(self):
|
||||
self.next_button.setEnabled(True)
|
||||
|
||||
@ -469,17 +874,79 @@ class FullFetch(QDialog): # {{{
|
||||
self.identify_widget.get_result()
|
||||
|
||||
def ok_clicked(self, *args):
|
||||
pass
|
||||
self.cover_pixmap = self.covers_widget.cover_pixmap()
|
||||
if DEBUG_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
|
||||
self.identify_widget.start(title=title, authors=authors,
|
||||
identifiers=identifiers)
|
||||
self.exec_()
|
||||
return self.exec_()
|
||||
# }}}
|
||||
|
||||
class CoverFetch(QDialog): # {{{
|
||||
|
||||
def __init__(self, current_cover=None, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.current_cover = current_cover
|
||||
self.log = Log()
|
||||
self.cover_pixmap = None
|
||||
|
||||
self.setWindowTitle(_('Downloading cover...'))
|
||||
self.setWindowIcon(QIcon(I('book.png')))
|
||||
|
||||
self.l = l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
|
||||
self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self)
|
||||
self.covers_widget.chosen.connect(self.accept)
|
||||
l.addWidget(self.covers_widget)
|
||||
|
||||
self.resize(850, 550)
|
||||
|
||||
self.finished.connect(self.cleanup)
|
||||
|
||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
|
||||
l.addWidget(self.bb)
|
||||
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.bb.rejected.connect(self.reject)
|
||||
self.bb.accepted.connect(self.accept)
|
||||
|
||||
def cleanup(self):
|
||||
self.covers_widget.cleanup()
|
||||
|
||||
def reject(self):
|
||||
self.covers_widget.cancel()
|
||||
return QDialog.reject(self)
|
||||
|
||||
def accept(self, *args):
|
||||
self.cover_pixmap = self.covers_widget.cover_pixmap()
|
||||
QDialog.accept(self)
|
||||
|
||||
def start(self, title, authors, identifiers):
|
||||
book = Metadata(title, authors)
|
||||
book.identifiers = identifiers
|
||||
self.covers_widget.start(book, self.current_cover,
|
||||
title, authors)
|
||||
return self.exec_()
|
||||
|
||||
def view_log(self):
|
||||
self._lv = LogViewer(self.log, self)
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
#DEBUG_DIALOG = True
|
||||
app = QApplication([])
|
||||
d = FullFetch(Log())
|
||||
d.start(title='great gatsby', authors=['Fitzgerald'])
|
||||
d = FullFetch()
|
||||
d.start(title='great gatsby', authors=['fitzgerald'])
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
<item row="2" column="2">
|
||||
<widget class="QCheckBox" name="opt_bools_are_tristate">
|
||||
<property name="text">
|
||||
<string>Custom Yes/No columns have three values (Requires restart):</string>
|
||||
<string>Yes/No columns have three values (Requires restart)</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>If checked, Yes/No custom columns values can be Yes, No, or Unknown.
|
||||
|
@ -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:
|
||||
|
@ -66,7 +66,7 @@ class HTMLStream(Stream):
|
||||
color = {
|
||||
DEBUG: '<span style="color:green">',
|
||||
INFO:'<span>',
|
||||
WARN: '<span style="color:yellow">',
|
||||
WARN: '<span style="color:blue">',
|
||||
ERROR: '<span style="color:red">'
|
||||
}
|
||||
normal = '</span>'
|
||||
|
Loading…
x
Reference in New Issue
Block a user