mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Merge from main branch
This commit is contained in:
commit
6ffb782ee9
@ -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
|
||||
|
@ -170,8 +170,8 @@ from setup import __appname__, __version__ as version
|
||||
# there.
|
||||
pot_header = '''\
|
||||
# Translation template file..
|
||||
# Copyright (C) 2007 Kovid Goyal
|
||||
# Kovid Goyal <kovid@kovidgoyal.net>, 2007.
|
||||
# Copyright (C) %(year)s Kovid Goyal
|
||||
# Kovid Goyal <kovid@kovidgoyal.net>, %(year)s.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
@ -185,7 +185,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\\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=''):
|
||||
|
@ -26,6 +26,38 @@ class POT(Command):
|
||||
ans.append(os.path.abspath(os.path.join(root, name)))
|
||||
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):
|
||||
files = self.source_files()
|
||||
@ -35,10 +67,10 @@ class POT(Command):
|
||||
atexit.register(shutil.rmtree, tempdir)
|
||||
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
||||
src = buf.getvalue()
|
||||
src += '\n\n' + self.get_tweaks_docs()
|
||||
pot = os.path.join(self.PATH, __appname__+'.pot')
|
||||
f = open(pot, 'wb')
|
||||
with open(pot, 'wb') as f:
|
||||
f.write(src)
|
||||
f.close()
|
||||
self.info('Translations template:', os.path.abspath(pot))
|
||||
return pot
|
||||
|
||||
|
@ -173,7 +173,7 @@ class ComicMetadataReader(MetadataReaderPlugin):
|
||||
stream.seek(pos)
|
||||
if id_ == b'Rar':
|
||||
ftype = 'cbr'
|
||||
elif id.startswith(b'PK'):
|
||||
elif id_.startswith(b'PK'):
|
||||
ftype = 'cbz'
|
||||
if ftype == 'cbr':
|
||||
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, '
|
||||
'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):
|
||||
name = 'Plugins'
|
||||
icon = I('plugins.png')
|
||||
@ -1076,6 +1087,9 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions,
|
||||
CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard,
|
||||
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)
|
||||
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([
|
||||
'Douban Books', 'Douban.com covers', 'Nicebooks', 'Nicebooks covers',
|
||||
'Kent District Library'
|
||||
@ -453,12 +464,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 {{{
|
||||
|
@ -37,7 +37,7 @@ class ANDROID(USBMS):
|
||||
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
|
||||
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
|
||||
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
|
||||
0x7086 : [0x0226],
|
||||
0x7086 : [0x0226], 0x70a8: [0x9999],
|
||||
},
|
||||
|
||||
# Sony Ericsson
|
||||
@ -96,7 +96,8 @@ class ANDROID(USBMS):
|
||||
|
||||
VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER',
|
||||
'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',
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||
@ -104,7 +105,7 @@ class ANDROID(USBMS):
|
||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
|
||||
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
|
||||
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
|
||||
'MB860']
|
||||
'MB860', 'MULTI-CARD']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S', 'A101IT', '7']
|
||||
|
@ -155,7 +155,7 @@ class FB2Output(OutputFormatPlugin):
|
||||
OptionRecommendation(name='fb2_genre',
|
||||
recommended_value='antique', level=OptionRecommendation.LOW,
|
||||
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.')),
|
||||
])
|
||||
|
||||
|
@ -279,7 +279,7 @@ class Worker(Thread): # Get details {{{
|
||||
|
||||
class Amazon(Source):
|
||||
|
||||
name = 'Amazon Web'
|
||||
name = 'Amazon.com'
|
||||
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')
|
||||
|
||||
@ -333,9 +341,10 @@ class Amazon(Source):
|
||||
# Insufficient metadata to make an identify query
|
||||
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()])
|
||||
url = 'http://www.amazon.%s/s/?'%domain + urlencode(utf8q)
|
||||
url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q)
|
||||
return url
|
||||
|
||||
# }}}
|
||||
|
@ -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,6 +238,10 @@ class GoogleBooks(Source):
|
||||
log('Downloading cover from:', cached_url)
|
||||
try:
|
||||
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))
|
||||
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
|
||||
|
@ -193,7 +193,10 @@ class PluginWidget(QWidget,Ui_Form):
|
||||
opts_dict['header_note_source_field'] = self.header_note_source_field_name
|
||||
|
||||
# Append the output profile
|
||||
try:
|
||||
opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']]
|
||||
except:
|
||||
opts_dict['output_profile'] = ['default']
|
||||
if False:
|
||||
print "opts_dict"
|
||||
for opt in sorted(opts_dict.keys()):
|
||||
|
@ -604,7 +604,10 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def size(r, idx=-1):
|
||||
size = self.db.data[r][idx]
|
||||
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
|
||||
|
||||
def rating_type(r, idx=-1):
|
||||
|
@ -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)
|
||||
@ -172,6 +172,7 @@ class AuthorsEdit(MultiCompleteComboBox):
|
||||
self.books_to_refresh = set([])
|
||||
all_authors = db.all_authors()
|
||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||
self.clear()
|
||||
for i in all_authors:
|
||||
id, name = i
|
||||
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||
@ -315,7 +316,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)
|
||||
|
||||
@ -326,6 +327,7 @@ class SeriesEdit(MultiCompleteComboBox):
|
||||
self.update_items_cache([x[1] for x in all_series])
|
||||
series_id = db.series_id(id_, index_is_id=True)
|
||||
idx, c = None, 0
|
||||
self.clear()
|
||||
for i in all_series:
|
||||
id, name = i
|
||||
if id == series_id:
|
||||
@ -613,6 +615,8 @@ class FormatsManager(QWidget): # {{{
|
||||
|
||||
class Cover(ImageView): # {{{
|
||||
|
||||
download_cover = pyqtSignal()
|
||||
|
||||
def __init__(self, parent):
|
||||
ImageView.__init__(self, parent)
|
||||
self.dialog = parent
|
||||
@ -703,9 +707,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 +863,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 +930,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 +980,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)
|
||||
|
||||
@ -987,13 +990,13 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
|
||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||
self.update_items_cache([x[1] for x in all_publishers])
|
||||
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||
idx, c = None, 0
|
||||
for i in all_publishers:
|
||||
id, name = i
|
||||
if id == publisher_id:
|
||||
idx = c
|
||||
idx = None
|
||||
self.clear()
|
||||
for i, x in enumerate(all_publishers):
|
||||
id_, name = x
|
||||
if id_ == publisher_id:
|
||||
idx = i
|
||||
self.addItem(name)
|
||||
c += 1
|
||||
|
||||
self.setEditText('')
|
||||
if idx is not None:
|
||||
|
@ -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):
|
||||
@ -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): # {{{
|
||||
|
||||
cc_two_column = False
|
||||
one_line_comments_toolbar = True
|
||||
|
||||
on_drag_enter = pyqtSignal()
|
||||
|
||||
def handle_drag_enter(self):
|
||||
self.central_widget.setCurrentIndex(1)
|
||||
|
||||
def do_layout(self):
|
||||
self.central_widget.clear()
|
||||
self.tabs = []
|
||||
self.labels = []
|
||||
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.tabs[0].l = QGridLayout()
|
||||
self.tabs[0].setLayout(self.tabs[0].l)
|
||||
@ -542,6 +580,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||
self.tabs[1].l = QGridLayout()
|
||||
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
|
||||
tab0 = self.tabs[0]
|
||||
|
||||
@ -550,6 +592,8 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||
self.tabs[0].l.addWidget(gb, 0, 0, 1, 1)
|
||||
gb.setLayout(tl)
|
||||
|
||||
self.button_box.addButton(self.fetch_metadata_button,
|
||||
QDialogButtonBox.ActionRole)
|
||||
sto(self.button_box, self.title)
|
||||
|
||||
def create_row(row, widget, tab_to, button=None, icon=None, span=1):
|
||||
@ -639,7 +683,6 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||
wgl.addWidget(gb)
|
||||
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
wgl.addWidget(self.fetch_metadata_button)
|
||||
wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
wgl.addWidget(self.formats_manager)
|
||||
|
@ -7,6 +7,9 @@ __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
|
||||
@ -21,14 +24,14 @@ from PyQt4.QtWebKit import QWebView
|
||||
from calibre.customize.ui import metadata_plugins
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.utils.logging import GUILog as Log
|
||||
from calibre.ebooks.metadata.sources.identify import identify
|
||||
from calibre.ebooks.metadata.sources.identify import (identify,
|
||||
urls_from_identifiers)
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.gui2 import error_dialog, NONE
|
||||
from calibre.utils.date import utcnow, fromordinal, format_date
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre import force_unicode
|
||||
|
||||
DEBUG_DIALOG = False
|
||||
# }}}
|
||||
|
||||
class RichTextDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
@ -41,7 +44,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
|
||||
|
||||
@ -174,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):
|
||||
@ -183,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:
|
||||
@ -234,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:
|
||||
@ -265,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)
|
||||
|
||||
@ -382,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">
|
||||
@ -409,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()))
|
||||
@ -541,16 +567,23 @@ class CoversModel(QAbstractListModel): # {{{
|
||||
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 = {}
|
||||
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]:
|
||||
good.append(x)
|
||||
if i > 0:
|
||||
plugin = self.plugin_for_index(i)
|
||||
pmap[plugin] = len(good) - 1
|
||||
good = [x for x in self.covers if not x[-1]]
|
||||
self.covers = good
|
||||
self.plugin_map = pmap
|
||||
self.reset()
|
||||
@ -645,7 +678,8 @@ class CoversWidget(QWidget): # {{{
|
||||
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()
|
||||
|
||||
@ -729,6 +763,10 @@ class LogViewer(QDialog): # {{{
|
||||
|
||||
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)
|
||||
|
||||
@ -739,10 +777,13 @@ class LogViewer(QDialog): # {{{
|
||||
self.keep_updating = True
|
||||
self.last_html = None
|
||||
self.finished.connect(self.stop)
|
||||
QTimer.singleShot(1000, self.update_log)
|
||||
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
|
||||
|
||||
@ -752,16 +793,17 @@ class LogViewer(QDialog): # {{{
|
||||
html = self.log.html
|
||||
if html != self.last_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)
|
||||
|
||||
# }}}
|
||||
|
||||
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...'))
|
||||
@ -787,7 +829,7 @@ class FullFetch(QDialog): # {{{
|
||||
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)
|
||||
@ -809,6 +851,7 @@ class FullFetch(QDialog): # {{{
|
||||
self.ok_button.setVisible(True)
|
||||
self.book = book
|
||||
self.stack.setCurrentIndex(1)
|
||||
self.log('\n\n')
|
||||
self.covers_widget.start(book, self.current_cover,
|
||||
self.title, self.authors)
|
||||
|
||||
@ -818,6 +861,7 @@ class FullFetch(QDialog): # {{{
|
||||
|
||||
def reject(self):
|
||||
self.identify_widget.cancel()
|
||||
self.covers_widget.cancel()
|
||||
return QDialog.reject(self)
|
||||
|
||||
def cleanup(self):
|
||||
@ -844,12 +888,65 @@ class FullFetch(QDialog): # {{{
|
||||
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
|
||||
#DEBUG_DIALOG = True
|
||||
app = QApplication([])
|
||||
d = FullFetch(Log())
|
||||
d.start(title='great gatsby', authors=['Fitzgerald'])
|
||||
d = FullFetch()
|
||||
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
|
||||
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.
|
||||
'''
|
||||
|
||||
|
@ -43,6 +43,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
r('overwrite_author_title_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('upload_news_to_device', 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 import isbytestring
|
||||
|
||||
from PyQt4.Qt import QAbstractListModel, Qt, QStyledItemDelegate, QStyle, \
|
||||
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog, \
|
||||
QVBoxLayout, QPlainTextEdit, QLabel
|
||||
from PyQt4.Qt import (QAbstractListModel, Qt, QStyledItemDelegate, QStyle,
|
||||
QStyleOptionViewItem, QFont, QDialogButtonBox, QDialog,
|
||||
QVBoxLayout, QPlainTextEdit, QLabel)
|
||||
|
||||
class Delegate(QStyledItemDelegate): # {{{
|
||||
def __init__(self, view):
|
||||
@ -35,8 +35,9 @@ class Delegate(QStyledItemDelegate): # {{{
|
||||
class Tweak(object): # {{{
|
||||
|
||||
def __init__(self, name, doc, var_names, defaults, custom):
|
||||
self.name = name
|
||||
self.doc = doc.strip()
|
||||
translate = __builtins__['_']
|
||||
self.name = translate(name)
|
||||
self.doc = translate(doc.strip())
|
||||
self.var_names = var_names
|
||||
self.default_values = {}
|
||||
for x in var_names:
|
||||
|
@ -1518,7 +1518,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
if node.tag.category in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher') or \
|
||||
(fm['is_custom'] and \
|
||||
fm['datatype'] in ['text', 'rating', 'series']):
|
||||
fm['datatype'] in ['text', 'rating', 'series', 'enumeration']):
|
||||
ans |= Qt.ItemIsDropEnabled
|
||||
else:
|
||||
ans |= Qt.ItemIsDropEnabled
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -785,8 +785,6 @@ def write_tweaks(raw):
|
||||
|
||||
tweaks = read_tweaks()
|
||||
test_eight_code = tweaks.get('test_eight_code', False)
|
||||
# test_eight_code notes
|
||||
# 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