mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Merge from main branch
This commit is contained in:
commit
6ffb782ee9
@ -3,8 +3,7 @@
|
|||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010, Constantin Hofstetter <consti at consti.de>, Steffen Siebert <calibre at steffensiebert.de>'
|
__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 '''
|
''' http://brandeins.de - Wirtschaftsmagazin '''
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
@ -14,8 +13,8 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
|
|||||||
class BrandEins(BasicNewsRecipe):
|
class BrandEins(BasicNewsRecipe):
|
||||||
|
|
||||||
title = u'brand eins'
|
title = u'brand eins'
|
||||||
__author__ = 'Constantin Hofstetter'
|
__author__ = 'Constantin Hofstetter; Steffen Siebert'
|
||||||
description = u'Wirtschaftsmagazin'
|
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'
|
publisher ='brandeins.de'
|
||||||
category = 'politics, business, wirtschaft, Germany'
|
category = 'politics, business, wirtschaft, Germany'
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
|
@ -170,8 +170,8 @@ from setup import __appname__, __version__ as version
|
|||||||
# there.
|
# there.
|
||||||
pot_header = '''\
|
pot_header = '''\
|
||||||
# Translation template file..
|
# Translation template file..
|
||||||
# Copyright (C) 2007 Kovid Goyal
|
# Copyright (C) %(year)s Kovid Goyal
|
||||||
# Kovid Goyal <kovid@kovidgoyal.net>, 2007.
|
# Kovid Goyal <kovid@kovidgoyal.net>, %(year)s.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -185,7 +185,7 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\\n"
|
"Content-Transfer-Encoding: 8bit\\n"
|
||||||
"Generated-By: pygettext.py %%(version)s\\n"
|
"Generated-By: pygettext.py %%(version)s\\n"
|
||||||
|
|
||||||
'''%dict(appname=__appname__, version=version)
|
'''%dict(appname=__appname__, version=version, year=time.strftime('%Y'))
|
||||||
|
|
||||||
|
|
||||||
def usage(code, msg=''):
|
def usage(code, msg=''):
|
||||||
|
@ -26,6 +26,38 @@ class POT(Command):
|
|||||||
ans.append(os.path.abspath(os.path.join(root, name)))
|
ans.append(os.path.abspath(os.path.join(root, name)))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
def get_tweaks_docs(self):
|
||||||
|
path = self.a(self.j(self.SRC, '..', 'resources', 'default_tweaks.py'))
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
raw = f.read().decode('utf-8')
|
||||||
|
msgs = []
|
||||||
|
lines = list(raw.splitlines())
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith('#:'):
|
||||||
|
msgs.append((i, line[2:].strip()))
|
||||||
|
j = i
|
||||||
|
block = []
|
||||||
|
while True:
|
||||||
|
j += 1
|
||||||
|
line = lines[j]
|
||||||
|
if not line.startswith('#'):
|
||||||
|
break
|
||||||
|
block.append(line[1:].strip())
|
||||||
|
if block:
|
||||||
|
msgs.append((i+1, '\n'.join(block)))
|
||||||
|
|
||||||
|
ans = []
|
||||||
|
for lineno, msg in msgs:
|
||||||
|
ans.append('#: %s:%d'%(path, lineno))
|
||||||
|
slash = unichr(92)
|
||||||
|
msg = msg.replace(slash, slash*2).replace('"', r'\"').replace('\n',
|
||||||
|
r'\n').replace('\r', r'\r').replace('\t', r'\t')
|
||||||
|
ans.append('msgid "%s"'%msg)
|
||||||
|
ans.append('msgstr ""')
|
||||||
|
ans.append('')
|
||||||
|
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
files = self.source_files()
|
files = self.source_files()
|
||||||
@ -35,10 +67,10 @@ class POT(Command):
|
|||||||
atexit.register(shutil.rmtree, tempdir)
|
atexit.register(shutil.rmtree, tempdir)
|
||||||
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
||||||
src = buf.getvalue()
|
src = buf.getvalue()
|
||||||
|
src += '\n\n' + self.get_tweaks_docs()
|
||||||
pot = os.path.join(self.PATH, __appname__+'.pot')
|
pot = os.path.join(self.PATH, __appname__+'.pot')
|
||||||
f = open(pot, 'wb')
|
with open(pot, 'wb') as f:
|
||||||
f.write(src)
|
f.write(src)
|
||||||
f.close()
|
|
||||||
self.info('Translations template:', os.path.abspath(pot))
|
self.info('Translations template:', os.path.abspath(pot))
|
||||||
return pot
|
return pot
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ class ComicMetadataReader(MetadataReaderPlugin):
|
|||||||
stream.seek(pos)
|
stream.seek(pos)
|
||||||
if id_ == b'Rar':
|
if id_ == b'Rar':
|
||||||
ftype = 'cbr'
|
ftype = 'cbr'
|
||||||
elif id.startswith(b'PK'):
|
elif id_.startswith(b'PK'):
|
||||||
ftype = 'cbz'
|
ftype = 'cbz'
|
||||||
if ftype == 'cbr':
|
if ftype == 'cbr':
|
||||||
from calibre.libunrar import extract_first_alphabetically as extract_first
|
from calibre.libunrar import extract_first_alphabetically as extract_first
|
||||||
@ -1038,6 +1038,17 @@ class Server(PreferencesPlugin):
|
|||||||
'give you access to your calibre library from anywhere, '
|
'give you access to your calibre library from anywhere, '
|
||||||
'on any device, over the internet')
|
'on any device, over the internet')
|
||||||
|
|
||||||
|
class MetadataSources(PreferencesPlugin):
|
||||||
|
name = 'Metadata download'
|
||||||
|
icon = I('metadata.png')
|
||||||
|
gui_name = _('Metadata download')
|
||||||
|
category = 'Sharing'
|
||||||
|
gui_category = _('Sharing')
|
||||||
|
category_order = 4
|
||||||
|
name_order = 3
|
||||||
|
config_widget = 'calibre.gui2.preferences.metadata_sources'
|
||||||
|
description = _('Control how calibre downloads ebook metadata from the net')
|
||||||
|
|
||||||
class Plugins(PreferencesPlugin):
|
class Plugins(PreferencesPlugin):
|
||||||
name = 'Plugins'
|
name = 'Plugins'
|
||||||
icon = I('plugins.png')
|
icon = I('plugins.png')
|
||||||
@ -1076,6 +1087,9 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
|
|||||||
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
|
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
|
||||||
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions]
|
Email, Server, Plugins, Tweaks, Misc, TemplateFunctions]
|
||||||
|
|
||||||
|
if test_eight_code:
|
||||||
|
plugins.append(MetadataSources)
|
||||||
|
|
||||||
#}}}
|
#}}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +75,17 @@ def enable_plugin(plugin_or_name):
|
|||||||
ep.add(x)
|
ep.add(x)
|
||||||
config['enabled_plugins'] = ep
|
config['enabled_plugins'] = ep
|
||||||
|
|
||||||
|
def restore_plugin_state_to_default(plugin_or_name):
|
||||||
|
x = getattr(plugin_or_name, 'name', plugin_or_name)
|
||||||
|
dp = config['disabled_plugins']
|
||||||
|
if x in dp:
|
||||||
|
dp.remove(x)
|
||||||
|
config['disabled_plugins'] = dp
|
||||||
|
ep = config['enabled_plugins']
|
||||||
|
if x in ep:
|
||||||
|
ep.remove(x)
|
||||||
|
config['enabled_plugins'] = ep
|
||||||
|
|
||||||
default_disabled_plugins = set([
|
default_disabled_plugins = set([
|
||||||
'Douban Books', 'Douban.com covers', 'Nicebooks', 'Nicebooks covers',
|
'Douban Books', 'Douban.com covers', 'Nicebooks', 'Nicebooks covers',
|
||||||
'Kent District Library'
|
'Kent District Library'
|
||||||
@ -453,12 +464,15 @@ def epub_fixers():
|
|||||||
# Metadata sources2 {{{
|
# Metadata sources2 {{{
|
||||||
def metadata_plugins(capabilities):
|
def metadata_plugins(capabilities):
|
||||||
capabilities = frozenset(capabilities)
|
capabilities = frozenset(capabilities)
|
||||||
for plugin in _initialized_plugins:
|
for plugin in all_metadata_plugins():
|
||||||
if isinstance(plugin, Source) and \
|
if plugin.capabilities.intersection(capabilities) and \
|
||||||
plugin.capabilities.intersection(capabilities) and \
|
|
||||||
not is_disabled(plugin):
|
not is_disabled(plugin):
|
||||||
yield plugin
|
yield plugin
|
||||||
|
|
||||||
|
def all_metadata_plugins():
|
||||||
|
for plugin in _initialized_plugins:
|
||||||
|
if isinstance(plugin, Source):
|
||||||
|
yield plugin
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Initialize plugins {{{
|
# Initialize plugins {{{
|
||||||
|
@ -37,7 +37,7 @@ class ANDROID(USBMS):
|
|||||||
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
|
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
|
||||||
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
|
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
|
||||||
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
|
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
|
||||||
0x7086 : [0x0226],
|
0x7086 : [0x0226], 0x70a8: [0x9999],
|
||||||
},
|
},
|
||||||
|
|
||||||
# Sony Ericsson
|
# Sony Ericsson
|
||||||
@ -96,7 +96,8 @@ class ANDROID(USBMS):
|
|||||||
|
|
||||||
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
||||||
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS',
|
'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS',
|
||||||
'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA']
|
'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA',
|
||||||
|
'GENERIC-']
|
||||||
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
|
||||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||||
@ -104,7 +105,7 @@ class ANDROID(USBMS):
|
|||||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
||||||
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
||||||
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
||||||
'MB860']
|
'MB860', 'MULTI-CARD']
|
||||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||||
'A70S', 'A101IT', '7']
|
'A70S', 'A101IT', '7']
|
||||||
|
@ -155,7 +155,7 @@ class FB2Output(OutputFormatPlugin):
|
|||||||
OptionRecommendation(name='fb2_genre',
|
OptionRecommendation(name='fb2_genre',
|
||||||
recommended_value='antique', level=OptionRecommendation.LOW,
|
recommended_value='antique', level=OptionRecommendation.LOW,
|
||||||
choices=FB2_GENRES,
|
choices=FB2_GENRES,
|
||||||
help=_('Genre for the book. Choices: %s\n\n See: ' % FB2_GENRES) + 'http://www.fictionbook.org/index.php/Eng:FictionBook_2.1_genres ' \
|
help=(_('Genre for the book. Choices: %s\n\n See: ') % FB2_GENRES) + 'http://www.fictionbook.org/index.php/Eng:FictionBook_2.1_genres ' \
|
||||||
+ _('for a complete list with descriptions.')),
|
+ _('for a complete list with descriptions.')),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ class Worker(Thread): # Get details {{{
|
|||||||
|
|
||||||
class Amazon(Source):
|
class Amazon(Source):
|
||||||
|
|
||||||
name = 'Amazon Web'
|
name = 'Amazon.com'
|
||||||
description = _('Downloads metadata from Amazon')
|
description = _('Downloads metadata from Amazon')
|
||||||
|
|
||||||
capabilities = frozenset(['identify', 'cover'])
|
capabilities = frozenset(['identify', 'cover'])
|
||||||
@ -295,6 +295,14 @@ class Amazon(Source):
|
|||||||
'uk' : _('UK'),
|
'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={}): # {{{
|
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
||||||
domain = self.prefs.get('domain', 'com')
|
domain = self.prefs.get('domain', 'com')
|
||||||
|
|
||||||
@ -333,9 +341,10 @@ class Amazon(Source):
|
|||||||
# Insufficient metadata to make an identify query
|
# Insufficient metadata to make an identify query
|
||||||
return None
|
return None
|
||||||
|
|
||||||
utf8q = dict([(x.encode('utf-8'), y.encode('utf-8')) for x, y in
|
latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1',
|
||||||
|
'ignore')) for x, y in
|
||||||
q.iteritems()])
|
q.iteritems()])
|
||||||
url = 'http://www.amazon.%s/s/?'%domain + urlencode(utf8q)
|
url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -78,8 +78,8 @@ class InternalMetadataCompareKeyGen(object):
|
|||||||
exact_title = 1 if title and \
|
exact_title = 1 if title and \
|
||||||
cleanup_title(title) == cleanup_title(mi.title) else 2
|
cleanup_title(title) == cleanup_title(mi.title) else 2
|
||||||
|
|
||||||
has_cover = 2 if source_plugin.get_cached_cover_url(mi.identifiers)\
|
has_cover = 2 if (not source_plugin.cached_cover_url_is_reliable or
|
||||||
is None else 1
|
source_plugin.get_cached_cover_url(mi.identifiers) is None) else 1
|
||||||
|
|
||||||
self.base = (isbn, has_cover, all_fields, exact_title)
|
self.base = (isbn, has_cover, all_fields, exact_title)
|
||||||
self.comments_len = len(mi.comments.strip() if mi.comments else '')
|
self.comments_len = len(mi.comments.strip() if mi.comments else '')
|
||||||
@ -157,6 +157,12 @@ class Source(Plugin):
|
|||||||
#: correctly first
|
#: correctly first
|
||||||
supports_gzip_transfer_encoding = False
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
Plugin.__init__(self, *args, **kwargs)
|
Plugin.__init__(self, *args, **kwargs)
|
||||||
self._isbn_to_identifier_cache = {}
|
self._isbn_to_identifier_cache = {}
|
||||||
@ -301,6 +307,13 @@ class Source(Plugin):
|
|||||||
|
|
||||||
# Metadata API {{{
|
# 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):
|
def get_cached_cover_url(self, identifiers):
|
||||||
'''
|
'''
|
||||||
Return cached cover URL for the book identified by
|
Return cached cover URL for the book identified by
|
||||||
|
@ -7,7 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import time
|
import time, hashlib
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
@ -133,7 +133,7 @@ def to_metadata(browser, log, entry_, timeout): # {{{
|
|||||||
default = utcnow().replace(day=15)
|
default = utcnow().replace(day=15)
|
||||||
mi.pubdate = parse_date(pubdate, assume_utc=True, default=default)
|
mi.pubdate = parse_date(pubdate, assume_utc=True, default=default)
|
||||||
except:
|
except:
|
||||||
log.exception('Failed to parse pubdate')
|
log.error('Failed to parse pubdate %r'%pubdate)
|
||||||
|
|
||||||
# Ratings
|
# Ratings
|
||||||
for x in rating(extra):
|
for x in rating(extra):
|
||||||
@ -164,9 +164,18 @@ class GoogleBooks(Source):
|
|||||||
'comments', 'publisher', 'identifier:isbn', 'rating',
|
'comments', 'publisher', 'identifier:isbn', 'rating',
|
||||||
'identifier:google']) # language currently disabled
|
'identifier:google']) # language currently disabled
|
||||||
supports_gzip_transfer_encoding = True
|
supports_gzip_transfer_encoding = True
|
||||||
|
cached_cover_url_is_reliable = False
|
||||||
|
|
||||||
GOOGLE_COVER = 'http://books.google.com/books?id=%s&printsec=frontcover&img=1'
|
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={}): # {{{
|
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
|
||||||
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
|
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
|
||||||
isbn = check_isbn(identifiers.get('isbn', None))
|
isbn = check_isbn(identifiers.get('isbn', None))
|
||||||
@ -229,6 +238,10 @@ class GoogleBooks(Source):
|
|||||||
log('Downloading cover from:', cached_url)
|
log('Downloading cover from:', cached_url)
|
||||||
try:
|
try:
|
||||||
cdata = br.open_novisit(cached_url, timeout=timeout).read()
|
cdata = br.open_novisit(cached_url, timeout=timeout).read()
|
||||||
|
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))
|
result_queue.put((self, cdata))
|
||||||
except:
|
except:
|
||||||
log.exception('Failed to download cover from:', cached_url)
|
log.exception('Failed to download cover from:', cached_url)
|
||||||
|
@ -14,7 +14,7 @@ from threading import Thread
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from operator import attrgetter
|
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.sources.base import create_log, msprefs
|
||||||
from calibre.ebooks.metadata.xisbn import xisbn
|
from calibre.ebooks.metadata.xisbn import xisbn
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
@ -338,8 +338,9 @@ def identify(log, abort, # {{{
|
|||||||
|
|
||||||
for i, result in enumerate(presults):
|
for i, result in enumerate(presults):
|
||||||
result.relevance_in_source = i
|
result.relevance_in_source = i
|
||||||
result.has_cached_cover_url = \
|
result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable
|
||||||
plugin.get_cached_cover_url(result.identifiers) is not None
|
and plugin.get_cached_cover_url(result.identifiers) is not
|
||||||
|
None)
|
||||||
result.identify_plugin = plugin
|
result.identify_plugin = plugin
|
||||||
|
|
||||||
log('The identify phase took %.2f seconds'%(time.time() - start_time))
|
log('The identify phase took %.2f seconds'%(time.time() - start_time))
|
||||||
@ -366,6 +367,22 @@ def identify(log, abort, # {{{
|
|||||||
return results
|
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 {{{
|
if __name__ == '__main__': # tests {{{
|
||||||
# To run these test use: calibre-debug -e
|
# To run these test use: calibre-debug -e
|
||||||
# src/calibre/ebooks/metadata/sources/identify.py
|
# src/calibre/ebooks/metadata/sources/identify.py
|
||||||
|
@ -193,7 +193,10 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
||||||
|
|
||||||
# Append the output profile
|
# Append the output profile
|
||||||
|
try:
|
||||||
opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']]
|
opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']]
|
||||||
|
except:
|
||||||
|
opts_dict['output_profile'] = ['default']
|
||||||
if False:
|
if False:
|
||||||
print "opts_dict"
|
print "opts_dict"
|
||||||
for opt in sorted(opts_dict.keys()):
|
for opt in sorted(opts_dict.keys()):
|
||||||
|
@ -604,7 +604,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
def size(r, idx=-1):
|
def size(r, idx=-1):
|
||||||
size = self.db.data[r][idx]
|
size = self.db.data[r][idx]
|
||||||
if size:
|
if size:
|
||||||
return QVariant('%.1f'%(float(size)/(1024*1024)))
|
ans = '%.1f'%(float(size)/(1024*1024))
|
||||||
|
if size > 0 and ans == '0.0':
|
||||||
|
ans = '<0.1'
|
||||||
|
return QVariant(ans)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def rating_type(r, idx=-1):
|
def rating_type(r, idx=-1):
|
||||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import textwrap, re, os
|
import textwrap, re, os
|
||||||
|
|
||||||
from PyQt4.Qt import (Qt, QDateEdit, QDate,
|
from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal,
|
||||||
QIcon, QToolButton, QWidget, QLabel, QGridLayout,
|
QIcon, QToolButton, QWidget, QLabel, QGridLayout,
|
||||||
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap,
|
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap,
|
||||||
QPushButton, QSpinBox, QLineEdit, QSizePolicy)
|
QPushButton, QSpinBox, QLineEdit, QSizePolicy)
|
||||||
@ -172,6 +172,7 @@ class AuthorsEdit(MultiCompleteComboBox):
|
|||||||
self.books_to_refresh = set([])
|
self.books_to_refresh = set([])
|
||||||
all_authors = db.all_authors()
|
all_authors = db.all_authors()
|
||||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
self.clear()
|
||||||
for i in all_authors:
|
for i in all_authors:
|
||||||
id, name = i
|
id, name = i
|
||||||
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||||
@ -315,7 +316,7 @@ class SeriesEdit(MultiCompleteComboBox):
|
|||||||
if not val:
|
if not val:
|
||||||
val = ''
|
val = ''
|
||||||
self.setEditText(val.strip())
|
self.setEditText(val.strip())
|
||||||
self.setCursorPosition(0)
|
self.lineEdit().setCursorPosition(0)
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@ -326,6 +327,7 @@ class SeriesEdit(MultiCompleteComboBox):
|
|||||||
self.update_items_cache([x[1] for x in all_series])
|
self.update_items_cache([x[1] for x in all_series])
|
||||||
series_id = db.series_id(id_, index_is_id=True)
|
series_id = db.series_id(id_, index_is_id=True)
|
||||||
idx, c = None, 0
|
idx, c = None, 0
|
||||||
|
self.clear()
|
||||||
for i in all_series:
|
for i in all_series:
|
||||||
id, name = i
|
id, name = i
|
||||||
if id == series_id:
|
if id == series_id:
|
||||||
@ -613,6 +615,8 @@ class FormatsManager(QWidget): # {{{
|
|||||||
|
|
||||||
class Cover(ImageView): # {{{
|
class Cover(ImageView): # {{{
|
||||||
|
|
||||||
|
download_cover = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
ImageView.__init__(self, parent)
|
ImageView.__init__(self, parent)
|
||||||
self.dialog = parent
|
self.dialog = parent
|
||||||
@ -703,9 +707,6 @@ class Cover(ImageView): # {{{
|
|||||||
cdata = im.export('png')
|
cdata = im.export('png')
|
||||||
self.current_val = cdata
|
self.current_val = cdata
|
||||||
|
|
||||||
def download_cover(self, *args):
|
|
||||||
pass # TODO: Implement this
|
|
||||||
|
|
||||||
def generate_cover(self, *args):
|
def generate_cover(self, *args):
|
||||||
from calibre.ebooks import calibre_cover
|
from calibre.ebooks import calibre_cover
|
||||||
from calibre.ebooks.metadata import fmt_sidx
|
from calibre.ebooks.metadata import fmt_sidx
|
||||||
@ -862,6 +863,7 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
|||||||
if not val:
|
if not val:
|
||||||
val = []
|
val = []
|
||||||
self.setText(', '.join([x.strip() for x in val]))
|
self.setText(', '.join([x.strip() for x in val]))
|
||||||
|
self.setCursorPosition(0)
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
@ -928,6 +930,7 @@ class IdentifiersEdit(QLineEdit): # {{{
|
|||||||
val = {}
|
val = {}
|
||||||
txt = ', '.join(['%s:%s'%(k, v) for k, v in val.iteritems()])
|
txt = ', '.join(['%s:%s'%(k, v) for k, v in val.iteritems()])
|
||||||
self.setText(txt.strip())
|
self.setText(txt.strip())
|
||||||
|
self.setCursorPosition(0)
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
@ -977,7 +980,7 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
|
|||||||
if not val:
|
if not val:
|
||||||
val = ''
|
val = ''
|
||||||
self.setEditText(val.strip())
|
self.setEditText(val.strip())
|
||||||
self.setCursorPosition(0)
|
self.lineEdit().setCursorPosition(0)
|
||||||
|
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@ -987,13 +990,13 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
|
|||||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||||
self.update_items_cache([x[1] for x in all_publishers])
|
self.update_items_cache([x[1] for x in all_publishers])
|
||||||
publisher_id = db.publisher_id(id_, index_is_id=True)
|
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||||
idx, c = None, 0
|
idx = None
|
||||||
for i in all_publishers:
|
self.clear()
|
||||||
id, name = i
|
for i, x in enumerate(all_publishers):
|
||||||
if id == publisher_id:
|
id_, name = x
|
||||||
idx = c
|
if id_ == publisher_id:
|
||||||
|
idx = i
|
||||||
self.addItem(name)
|
self.addItem(name)
|
||||||
c += 1
|
|
||||||
|
|
||||||
self.setEditText('')
|
self.setEditText('')
|
||||||
if idx is not None:
|
if idx is not None:
|
||||||
|
@ -16,11 +16,12 @@ from PyQt4.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton,
|
|||||||
QSizePolicy, QPalette, QFrame, QSize, QKeySequence)
|
QSizePolicy, QPalette, QFrame, QSize, QKeySequence)
|
||||||
|
|
||||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
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,
|
from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit,
|
||||||
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit,
|
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, IdentifiersEdit,
|
||||||
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit,
|
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit,
|
||||||
BuddyLabel, DateEdit, PubdateEdit)
|
BuddyLabel, DateEdit, PubdateEdit)
|
||||||
|
from calibre.gui2.metadata.single_download import FullFetch
|
||||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
|
|
||||||
@ -132,6 +133,7 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
self.formats_manager.cover_from_format_button.clicked.connect(
|
self.formats_manager.cover_from_format_button.clicked.connect(
|
||||||
self.cover_from_format)
|
self.cover_from_format)
|
||||||
self.cover = Cover(self)
|
self.cover = Cover(self)
|
||||||
|
self.cover.download_cover.connect(self.download_cover)
|
||||||
self.basic_metadata_widgets.append(self.cover)
|
self.basic_metadata_widgets.append(self.cover)
|
||||||
|
|
||||||
self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
|
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.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
|
||||||
|
|
||||||
self.fetch_metadata_button = QPushButton(
|
self.fetch_metadata_button = QPushButton(
|
||||||
_('&Fetch metadata from server'), self)
|
_('&Download metadata'), self)
|
||||||
self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
|
self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
|
||||||
font = self.fmb_font = QFont()
|
font = self.fmb_font = QFont()
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
@ -303,7 +305,26 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
self.comments.current_val = mi.comments
|
self.comments.current_val = mi.comments
|
||||||
|
|
||||||
def fetch_metadata(self, *args):
|
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):
|
def apply_changes(self):
|
||||||
@ -521,18 +542,35 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class DragTrackingWidget(QWidget): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent, on_drag_enter):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.on_drag_enter = on_drag_enter
|
||||||
|
|
||||||
|
def dragEnterEvent(self, ev):
|
||||||
|
self.on_drag_enter.emit()
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||||
|
|
||||||
cc_two_column = False
|
cc_two_column = False
|
||||||
one_line_comments_toolbar = True
|
one_line_comments_toolbar = True
|
||||||
|
|
||||||
|
on_drag_enter = pyqtSignal()
|
||||||
|
|
||||||
|
def handle_drag_enter(self):
|
||||||
|
self.central_widget.setCurrentIndex(1)
|
||||||
|
|
||||||
def do_layout(self):
|
def do_layout(self):
|
||||||
self.central_widget.clear()
|
self.central_widget.clear()
|
||||||
self.tabs = []
|
self.tabs = []
|
||||||
self.labels = []
|
self.labels = []
|
||||||
sto = QWidget.setTabOrder
|
sto = QWidget.setTabOrder
|
||||||
|
|
||||||
self.tabs.append(QWidget(self))
|
self.on_drag_enter.connect(self.handle_drag_enter)
|
||||||
|
self.tabs.append(DragTrackingWidget(self, self.on_drag_enter))
|
||||||
self.central_widget.addTab(self.tabs[0], _("&Metadata"))
|
self.central_widget.addTab(self.tabs[0], _("&Metadata"))
|
||||||
self.tabs[0].l = QGridLayout()
|
self.tabs[0].l = QGridLayout()
|
||||||
self.tabs[0].setLayout(self.tabs[0].l)
|
self.tabs[0].setLayout(self.tabs[0].l)
|
||||||
@ -542,6 +580,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
|||||||
self.tabs[1].l = QGridLayout()
|
self.tabs[1].l = QGridLayout()
|
||||||
self.tabs[1].setLayout(self.tabs[1].l)
|
self.tabs[1].setLayout(self.tabs[1].l)
|
||||||
|
|
||||||
|
# accept drop events so we can automatically switch to the second tab to
|
||||||
|
# drop covers and formats
|
||||||
|
self.tabs[0].setAcceptDrops(True)
|
||||||
|
|
||||||
# Tab 0
|
# Tab 0
|
||||||
tab0 = self.tabs[0]
|
tab0 = self.tabs[0]
|
||||||
|
|
||||||
@ -550,6 +592,8 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
|||||||
self.tabs[0].l.addWidget(gb, 0, 0, 1, 1)
|
self.tabs[0].l.addWidget(gb, 0, 0, 1, 1)
|
||||||
gb.setLayout(tl)
|
gb.setLayout(tl)
|
||||||
|
|
||||||
|
self.button_box.addButton(self.fetch_metadata_button,
|
||||||
|
QDialogButtonBox.ActionRole)
|
||||||
sto(self.button_box, self.title)
|
sto(self.button_box, self.title)
|
||||||
|
|
||||||
def create_row(row, widget, tab_to, button=None, icon=None, span=1):
|
def create_row(row, widget, tab_to, button=None, icon=None, span=1):
|
||||||
@ -639,7 +683,6 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
|||||||
wgl.addWidget(gb)
|
wgl.addWidget(gb)
|
||||||
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
QSizePolicy.Expanding))
|
QSizePolicy.Expanding))
|
||||||
wgl.addWidget(self.fetch_metadata_button)
|
|
||||||
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
QSizePolicy.Expanding))
|
QSizePolicy.Expanding))
|
||||||
wgl.addWidget(self.formats_manager)
|
wgl.addWidget(self.formats_manager)
|
||||||
|
@ -7,6 +7,9 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
DEBUG_DIALOG = False
|
||||||
|
|
||||||
|
# Imports {{{
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
@ -21,14 +24,14 @@ from PyQt4.QtWebKit import QWebView
|
|||||||
from calibre.customize.ui import metadata_plugins
|
from calibre.customize.ui import metadata_plugins
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre.utils.logging import GUILog as Log
|
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.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.gui2 import error_dialog, NONE
|
from calibre.gui2 import error_dialog, NONE
|
||||||
from calibre.utils.date import utcnow, fromordinal, format_date
|
from calibre.utils.date import utcnow, fromordinal, format_date
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
|
# }}}
|
||||||
DEBUG_DIALOG = False
|
|
||||||
|
|
||||||
class RichTextDelegate(QStyledItemDelegate): # {{{
|
class RichTextDelegate(QStyledItemDelegate): # {{{
|
||||||
|
|
||||||
@ -41,7 +44,10 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
def sizeHint(self, option, index):
|
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)
|
ans.setHeight(ans.height()+10)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@ -174,6 +180,13 @@ class ResultsModel(QAbstractTableModel): # {{{
|
|||||||
return self.yes_icon
|
return self.yes_icon
|
||||||
elif role == Qt.UserRole:
|
elif role == Qt.UserRole:
|
||||||
return book
|
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
|
return NONE
|
||||||
|
|
||||||
def sort(self, col, order=Qt.AscendingOrder):
|
def sort(self, col, order=Qt.AscendingOrder):
|
||||||
@ -183,7 +196,7 @@ class ResultsModel(QAbstractTableModel): # {{{
|
|||||||
elif col == 1:
|
elif col == 1:
|
||||||
key = attrgetter('title')
|
key = attrgetter('title')
|
||||||
elif col == 2:
|
elif col == 2:
|
||||||
key = attrgetter('authors')
|
key = attrgetter('pubdate')
|
||||||
elif col == 3:
|
elif col == 3:
|
||||||
key = attrgetter('has_cached_cover_url')
|
key = attrgetter('has_cached_cover_url')
|
||||||
elif key == 4:
|
elif key == 4:
|
||||||
@ -234,6 +247,11 @@ class ResultsView(QTableView): # {{{
|
|||||||
if not book.is_null('rating'):
|
if not book.is_null('rating'):
|
||||||
parts.append('<div>%s</div>'%('\u2605'*int(book.rating)))
|
parts.append('<div>%s</div>'%('\u2605'*int(book.rating)))
|
||||||
parts.append('</center>')
|
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:
|
if book.tags:
|
||||||
parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags))
|
parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags))
|
||||||
if book.comments:
|
if book.comments:
|
||||||
@ -265,6 +283,14 @@ class Comments(QWebView): # {{{
|
|||||||
self.page().setPalette(palette)
|
self.page().setPalette(palette)
|
||||||
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
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):
|
def turnoff_scrollbar(self, *args):
|
||||||
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
@ -382,7 +408,7 @@ class IdentifyWidget(QWidget): # {{{
|
|||||||
self.query.setWordWrap(True)
|
self.query.setWordWrap(True)
|
||||||
l.addWidget(self.query, 2, 0, 1, 2)
|
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>'+
|
'<br><span id="dots">.</span></h2>'+
|
||||||
'''
|
'''
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -409,7 +435,7 @@ class IdentifyWidget(QWidget): # {{{
|
|||||||
if authors:
|
if authors:
|
||||||
parts.append('authors:'+authors_to_string(authors))
|
parts.append('authors:'+authors_to_string(authors))
|
||||||
if identifiers:
|
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)
|
parts.append(x)
|
||||||
self.query.setText(_('Query: ')+'; '.join(parts))
|
self.query.setText(_('Query: ')+'; '.join(parts))
|
||||||
self.log(unicode(self.query.text()))
|
self.log(unicode(self.query.text()))
|
||||||
@ -541,16 +567,23 @@ class CoversModel(QAbstractListModel): # {{{
|
|||||||
if v == row:
|
if v == row:
|
||||||
return k
|
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):
|
def clear_failed(self):
|
||||||
good = []
|
good = []
|
||||||
pmap = {}
|
pmap = {}
|
||||||
for i, x in enumerate(self.covers):
|
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]:
|
if not x[-1]:
|
||||||
good.append(x)
|
good.append(x)
|
||||||
if i > 0:
|
if i > 0:
|
||||||
plugin = self.plugin_for_index(i)
|
plugin = self.plugin_for_index(i)
|
||||||
pmap[plugin] = len(good) - 1
|
pmap[plugin] = len(good) - 1
|
||||||
good = [x for x in self.covers if not x[-1]]
|
|
||||||
self.covers = good
|
self.covers = good
|
||||||
self.plugin_map = pmap
|
self.plugin_map = pmap
|
||||||
self.reset()
|
self.reset()
|
||||||
@ -645,7 +678,8 @@ class CoversWidget(QWidget): # {{{
|
|||||||
def start(self, book, current_cover, title, authors):
|
def start(self, book, current_cover, title, authors):
|
||||||
self.book, self.current_cover = book, current_cover
|
self.book, self.current_cover = book, current_cover
|
||||||
self.title, self.authors = title, authors
|
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.msg.setText('<p>'+_('Downloading covers for <b>%s</b>, please wait...')%book.title)
|
||||||
self.covers_view.start()
|
self.covers_view.start()
|
||||||
|
|
||||||
@ -729,6 +763,10 @@ class LogViewer(QDialog): # {{{
|
|||||||
|
|
||||||
self.bb = QDialogButtonBox(QDialogButtonBox.Close)
|
self.bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
l.addWidget(self.bb)
|
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.rejected.connect(self.reject)
|
||||||
self.bb.accepted.connect(self.accept)
|
self.bb.accepted.connect(self.accept)
|
||||||
|
|
||||||
@ -739,10 +777,13 @@ class LogViewer(QDialog): # {{{
|
|||||||
self.keep_updating = True
|
self.keep_updating = True
|
||||||
self.last_html = None
|
self.last_html = None
|
||||||
self.finished.connect(self.stop)
|
self.finished.connect(self.stop)
|
||||||
QTimer.singleShot(1000, self.update_log)
|
QTimer.singleShot(100, self.update_log)
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
|
def copy_to_clipboard(self):
|
||||||
|
QApplication.clipboard().setText(''.join(self.log.plain_text))
|
||||||
|
|
||||||
def stop(self, *args):
|
def stop(self, *args):
|
||||||
self.keep_updating = False
|
self.keep_updating = False
|
||||||
|
|
||||||
@ -752,16 +793,17 @@ class LogViewer(QDialog): # {{{
|
|||||||
html = self.log.html
|
html = self.log.html
|
||||||
if html != self.last_html:
|
if html != self.last_html:
|
||||||
self.last_html = html
|
self.last_html = html
|
||||||
self.tb.setHtml('<pre>%s</pre>'%html)
|
self.tb.setHtml('<pre style="font-family:monospace">%s</pre>'%html)
|
||||||
QTimer.singleShot(1000, self.update_log)
|
QTimer.singleShot(1000, self.update_log)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class FullFetch(QDialog): # {{{
|
class FullFetch(QDialog): # {{{
|
||||||
|
|
||||||
def __init__(self, log, current_cover=None, parent=None):
|
def __init__(self, current_cover=None, parent=None):
|
||||||
QDialog.__init__(self, parent)
|
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.book = self.cover_pixmap = None
|
||||||
|
|
||||||
self.setWindowTitle(_('Downloading metadata...'))
|
self.setWindowTitle(_('Downloading metadata...'))
|
||||||
@ -787,7 +829,7 @@ class FullFetch(QDialog): # {{{
|
|||||||
self.log_button.setIcon(QIcon(I('debug.png')))
|
self.log_button.setIcon(QIcon(I('debug.png')))
|
||||||
self.ok_button.setVisible(False)
|
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.rejected.connect(self.reject)
|
||||||
self.identify_widget.results_found.connect(self.identify_results_found)
|
self.identify_widget.results_found.connect(self.identify_results_found)
|
||||||
self.identify_widget.book_selected.connect(self.book_selected)
|
self.identify_widget.book_selected.connect(self.book_selected)
|
||||||
@ -809,6 +851,7 @@ class FullFetch(QDialog): # {{{
|
|||||||
self.ok_button.setVisible(True)
|
self.ok_button.setVisible(True)
|
||||||
self.book = book
|
self.book = book
|
||||||
self.stack.setCurrentIndex(1)
|
self.stack.setCurrentIndex(1)
|
||||||
|
self.log('\n\n')
|
||||||
self.covers_widget.start(book, self.current_cover,
|
self.covers_widget.start(book, self.current_cover,
|
||||||
self.title, self.authors)
|
self.title, self.authors)
|
||||||
|
|
||||||
@ -818,6 +861,7 @@ class FullFetch(QDialog): # {{{
|
|||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
self.identify_widget.cancel()
|
self.identify_widget.cancel()
|
||||||
|
self.covers_widget.cancel()
|
||||||
return QDialog.reject(self)
|
return QDialog.reject(self)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
@ -844,12 +888,65 @@ class FullFetch(QDialog): # {{{
|
|||||||
self.title, self.authors = title, authors
|
self.title, self.authors = title, authors
|
||||||
self.identify_widget.start(title=title, authors=authors,
|
self.identify_widget.start(title=title, authors=authors,
|
||||||
identifiers=identifiers)
|
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__':
|
if __name__ == '__main__':
|
||||||
DEBUG_DIALOG = True
|
#DEBUG_DIALOG = True
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
d = FullFetch(Log())
|
d = FullFetch()
|
||||||
d.start(title='great gatsby', authors=['Fitzgerald'])
|
d.start(title='great gatsby', authors=['fitzgerald'])
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class ConfigWidgetInterface(object):
|
|||||||
'''
|
'''
|
||||||
This class defines the interface that all widgets displayed in the
|
This class defines the interface that all widgets displayed in the
|
||||||
Preferences dialog must implement. See :class:`ConfigWidgetBase` for
|
Preferences dialog must implement. See :class:`ConfigWidgetBase` for
|
||||||
a base class that implements this interface and defines various conveninece
|
a base class that implements this interface and defines various convenience
|
||||||
methods as well.
|
methods as well.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -43,6 +43,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||||||
|
|
||||||
r('overwrite_author_title_metadata', config)
|
r('overwrite_author_title_metadata', config)
|
||||||
r('get_social_metadata', config)
|
r('get_social_metadata', config)
|
||||||
|
if test_eight_code:
|
||||||
|
self.opt_overwrite_author_title_metadata.setVisible(False)
|
||||||
|
self.opt_get_social_metadata.setVisible(False)
|
||||||
r('new_version_notification', config)
|
r('new_version_notification', config)
|
||||||
r('upload_news_to_device', config)
|
r('upload_news_to_device', config)
|
||||||
r('delete_news_from_library_on_upload', config)
|
r('delete_news_from_library_on_upload', config)
|
||||||
|
190
src/calibre/gui2/preferences/metadata_sources.py
Normal file
190
src/calibre/gui2/preferences/metadata_sources.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
|
from PyQt4.Qt import (QAbstractTableModel, Qt, QAbstractListModel)
|
||||||
|
|
||||||
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||||
|
from calibre.gui2.preferences.metadata_sources_ui import Ui_Form
|
||||||
|
from calibre.ebooks.metadata.sources.base import msprefs
|
||||||
|
from calibre.customize.ui import (all_metadata_plugins, is_disabled,
|
||||||
|
enable_plugin, disable_plugin, restore_plugin_state_to_default)
|
||||||
|
from calibre.gui2 import NONE
|
||||||
|
|
||||||
|
class SourcesModel(QAbstractTableModel): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QAbstractTableModel.__init__(self, parent)
|
||||||
|
|
||||||
|
self.plugins = []
|
||||||
|
self.enabled_overrides = {}
|
||||||
|
self.cover_overrides = {}
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.plugins = list(all_metadata_plugins())
|
||||||
|
self.plugins.sort(key=attrgetter('name'))
|
||||||
|
self.enabled_overrides = {}
|
||||||
|
self.cover_overrides = {}
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def rowCount(self, parent=None):
|
||||||
|
return len(self.plugins)
|
||||||
|
|
||||||
|
def columnCount(self, parent=None):
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||||
|
if section == 0:
|
||||||
|
return _('Source')
|
||||||
|
if section == 1:
|
||||||
|
return _('Cover priority')
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
try:
|
||||||
|
plugin = self.plugins[index.row()]
|
||||||
|
except:
|
||||||
|
return NONE
|
||||||
|
col = index.column()
|
||||||
|
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
if col == 0:
|
||||||
|
return plugin.name
|
||||||
|
elif col == 1:
|
||||||
|
orig = msprefs['cover_priorities'].get(plugin.name, 1)
|
||||||
|
return self.cover_overrides.get(plugin, orig)
|
||||||
|
elif role == Qt.CheckStateRole and col == 0:
|
||||||
|
orig = Qt.Unchecked if is_disabled(plugin) else Qt.Checked
|
||||||
|
return self.enabled_overrides.get(plugin, orig)
|
||||||
|
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def setData(self, index, val, role):
|
||||||
|
try:
|
||||||
|
plugin = self.plugins[index.row()]
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
col = index.column()
|
||||||
|
ret = False
|
||||||
|
if col == 0 and role == Qt.CheckStateRole:
|
||||||
|
val, ok = val.toInt()
|
||||||
|
if ok:
|
||||||
|
self.enabled_overrides[plugin] = val
|
||||||
|
ret = True
|
||||||
|
if col == 1 and role == Qt.EditRole:
|
||||||
|
val, ok = val.toInt()
|
||||||
|
if ok:
|
||||||
|
self.cover_overrides[plugin] = val
|
||||||
|
ret = True
|
||||||
|
if ret:
|
||||||
|
self.dataChanged.emit(index, index)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
col = index.column()
|
||||||
|
ans = QAbstractTableModel.flags(self, index)
|
||||||
|
if col == 0:
|
||||||
|
return ans | Qt.ItemIsUserCheckable
|
||||||
|
return Qt.ItemIsEditable | ans
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
for plugin, val in self.enabled_overrides.iteritems():
|
||||||
|
if val == Qt.Checked:
|
||||||
|
enable_plugin(plugin)
|
||||||
|
elif val == Qt.Unchecked:
|
||||||
|
disable_plugin(plugin)
|
||||||
|
|
||||||
|
if self.cover_overrides:
|
||||||
|
cp = msprefs['cover_priorities']
|
||||||
|
for plugin, val in self.cover_overrides.iteritems():
|
||||||
|
if val == 1:
|
||||||
|
cp.pop(plugin.name, None)
|
||||||
|
else:
|
||||||
|
cp[plugin.name] = val
|
||||||
|
msprefs['cover_priorities'] = cp
|
||||||
|
|
||||||
|
self.enabled_overrides = {}
|
||||||
|
self.cover_overrides = {}
|
||||||
|
|
||||||
|
def restore_defaults(self):
|
||||||
|
del msprefs['cover_priorities']
|
||||||
|
self.enabled_overrides = {}
|
||||||
|
self.cover_overrides = {}
|
||||||
|
for plugin in self.plugins:
|
||||||
|
restore_plugin_state_to_default(plugin)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class FieldsModel(QAbstractListModel): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QAbstractTableModel.__init__(self, parent)
|
||||||
|
|
||||||
|
self.fields = []
|
||||||
|
|
||||||
|
def rowCount(self, parent=None):
|
||||||
|
return len(self.fields)
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
fields = set()
|
||||||
|
for p in all_metadata_plugins():
|
||||||
|
fields |= p.touched_fields
|
||||||
|
self.fields = []
|
||||||
|
for x in fields:
|
||||||
|
if not x.startswith('identifiers:'):
|
||||||
|
self.fields.append(x)
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||||
|
|
||||||
|
def genesis(self, gui):
|
||||||
|
r = self.register
|
||||||
|
r('txt_comments', msprefs)
|
||||||
|
r('max_tags', msprefs)
|
||||||
|
r('wait_after_first_identify_result', msprefs)
|
||||||
|
r('wait_after_first_cover_result', msprefs)
|
||||||
|
|
||||||
|
self.configure_plugin_button.clicked.connect(self.configure_plugin)
|
||||||
|
self.sources_model = SourcesModel(self)
|
||||||
|
self.sources_view.setModel(self.sources_model)
|
||||||
|
self.sources_model.dataChanged.connect(self.changed_signal)
|
||||||
|
|
||||||
|
self.fields_model = FieldsModel(self)
|
||||||
|
self.fields_view.setModel(self.fields_model)
|
||||||
|
self.fields_model.dataChanged.connect(self.changed_signal)
|
||||||
|
|
||||||
|
def configure_plugin(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
ConfigWidgetBase.initialize(self)
|
||||||
|
self.sources_model.initialize()
|
||||||
|
self.sources_view.resizeColumnsToContents()
|
||||||
|
|
||||||
|
def restore_defaults(self):
|
||||||
|
ConfigWidgetBase.restore_defaults(self)
|
||||||
|
self.sources_model.restore_defaults()
|
||||||
|
self.changed_signal.emit()
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
self.sources_model.commit()
|
||||||
|
return ConfigWidgetBase.commit(self)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from PyQt4.Qt import QApplication
|
||||||
|
app = QApplication([])
|
||||||
|
test_widget('Sharing', 'Metadata download')
|
||||||
|
|
146
src/calibre/gui2/preferences/metadata_sources.ui
Normal file
146
src/calibre/gui2/preferences/metadata_sources.ui
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>781</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stack">
|
||||||
|
<widget class="QWidget" name="page">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0" rowspan="5">
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Metadata sources</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Disable any metadata sources you do not want by unchecking them. You can also set the cover priority. Covers from sources that have a higher (smaller) priority will be preferred when bulk downloading metadata.
|
||||||
|
</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableView" name="sources_view">
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="configure_plugin_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>Configure selected source</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/plugins.png</normaloff>:/images/plugins.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" colspan="2">
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Downloaded metadata fields</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QListView" name="fields_view">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>If you uncheck any fields, metadata for those fields will not be downloaded</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="opt_txt_comments">
|
||||||
|
<property name="text">
|
||||||
|
<string>Convert all downloaded comments to plain &text</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max. number of &tags to download:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_max_tags</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QSpinBox" name="opt_max_tags"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max. &time to wait after first match is found:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_wait_after_first_identify_result</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<widget class="QSpinBox" name="opt_wait_after_first_identify_result">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> secs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Max. time to wait after first &cover is found:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_wait_after_first_cover_result</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="QSpinBox" name="opt_wait_after_first_cover_result">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> secs</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_2"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../../../../resources/images.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -14,9 +14,9 @@ from calibre.utils.config import read_raw_tweaks, write_tweaks
|
|||||||
from calibre.gui2.widgets import PythonHighlighter
|
from calibre.gui2.widgets import PythonHighlighter
|
||||||
from calibre import isbytestring
|
from calibre import isbytestring
|
||||||
|
|
||||||
from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \
|
from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle,
|
||||||
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \
|
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog,
|
||||||
QVBoxLayout, QPlainTextEdit, QLabel
|
QVBoxLayout, QPlainTextEdit, QLabel)
|
||||||
|
|
||||||
class Delegate(QStyledItemDelegate): # {{{
|
class Delegate(QStyledItemDelegate): # {{{
|
||||||
def __init__(self, view):
|
def __init__(self, view):
|
||||||
@ -35,8 +35,9 @@ class Delegate(QStyledItemDelegate): # {{{
|
|||||||
class Tweak(object): # {{{
|
class Tweak(object): # {{{
|
||||||
|
|
||||||
def __init__(self, name, doc, var_names, defaults, custom):
|
def __init__(self, name, doc, var_names, defaults, custom):
|
||||||
self.name = name
|
translate = __builtins__['_']
|
||||||
self.doc = doc.strip()
|
self.name = translate(name)
|
||||||
|
self.doc = translate(doc.strip())
|
||||||
self.var_names = var_names
|
self.var_names = var_names
|
||||||
self.default_values = {}
|
self.default_values = {}
|
||||||
for x in var_names:
|
for x in var_names:
|
||||||
|
@ -1518,7 +1518,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
if node.tag.category in \
|
if node.tag.category in \
|
||||||
('tags', 'series', 'authors', 'rating', 'publisher') or \
|
('tags', 'series', 'authors', 'rating', 'publisher') or \
|
||||||
(fm['is_custom'] and \
|
(fm['is_custom'] and \
|
||||||
fm['datatype'] in ['text', 'rating', 'series']):
|
fm['datatype'] in ['text', 'rating', 'series', 'enumeration']):
|
||||||
ans |= Qt.ItemIsDropEnabled
|
ans |= Qt.ItemIsDropEnabled
|
||||||
else:
|
else:
|
||||||
ans |= Qt.ItemIsDropEnabled
|
ans |= Qt.ItemIsDropEnabled
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -785,8 +785,6 @@ def write_tweaks(raw):
|
|||||||
|
|
||||||
tweaks = read_tweaks()
|
tweaks = read_tweaks()
|
||||||
test_eight_code = tweaks.get('test_eight_code', False)
|
test_eight_code = tweaks.get('test_eight_code', False)
|
||||||
# test_eight_code notes
|
|
||||||
# Change Amazon plugin name to just Amazon
|
|
||||||
|
|
||||||
def migrate():
|
def migrate():
|
||||||
if hasattr(os, 'geteuid') and os.geteuid() == 0:
|
if hasattr(os, 'geteuid') and os.geteuid() == 0:
|
||||||
|
@ -66,7 +66,7 @@ class HTMLStream(Stream):
|
|||||||
color = {
|
color = {
|
||||||
DEBUG: '<span style="color:green">',
|
DEBUG: '<span style="color:green">',
|
||||||
INFO:'<span>',
|
INFO:'<span>',
|
||||||
WARN: '<span style="color:yellow">',
|
WARN: '<span style="color:blue">',
|
||||||
ERROR: '<span style="color:red">'
|
ERROR: '<span style="color:red">'
|
||||||
}
|
}
|
||||||
normal = '</span>'
|
normal = '</span>'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user