mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Support for setting per book language
This commit is contained in:
parent
0dcdff7513
commit
478cc618f7
@ -291,6 +291,8 @@ class ISO639(Command):
|
|||||||
by_3t = {}
|
by_3t = {}
|
||||||
m2to3 = {}
|
m2to3 = {}
|
||||||
m3to2 = {}
|
m3to2 = {}
|
||||||
|
m3bto3t = {}
|
||||||
|
nm = {}
|
||||||
codes2, codes3t, codes3b = set([]), set([]), set([])
|
codes2, codes3t, codes3b = set([]), set([]), set([])
|
||||||
for x in root.xpath('//iso_639_entry'):
|
for x in root.xpath('//iso_639_entry'):
|
||||||
name = x.get('name')
|
name = x.get('name')
|
||||||
@ -304,12 +306,15 @@ class ISO639(Command):
|
|||||||
m3to2[threeb] = m3to2[threet] = two
|
m3to2[threeb] = m3to2[threet] = two
|
||||||
by_3b[threeb] = name
|
by_3b[threeb] = name
|
||||||
by_3t[threet] = name
|
by_3t[threet] = name
|
||||||
|
if threeb != threet:
|
||||||
|
m3bto3t[threeb] = threet
|
||||||
codes3b.add(x.get('iso_639_2B_code'))
|
codes3b.add(x.get('iso_639_2B_code'))
|
||||||
codes3t.add(x.get('iso_639_2T_code'))
|
codes3t.add(x.get('iso_639_2T_code'))
|
||||||
|
nm[name.lower().partition(';')[0].strip()] = threet
|
||||||
|
|
||||||
from cPickle import dump
|
from cPickle import dump
|
||||||
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
|
x = {'by_2':by_2, 'by_3b':by_3b, 'by_3t':by_3t, 'codes2':codes2,
|
||||||
'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3,
|
'codes3b':codes3b, 'codes3t':codes3t, '2to3':m2to3,
|
||||||
'3to2':m3to2}
|
'3to2':m3to2, '3bto3t':m3bto3t, 'name_map':nm}
|
||||||
dump(x, open(dest, 'wb'), -1)
|
dump(x, open(dest, 'wb'), -1)
|
||||||
|
|
||||||
|
@ -47,8 +47,7 @@ PUBLICATION_METADATA_FIELDS = frozenset([
|
|||||||
# If None, means book
|
# If None, means book
|
||||||
'publication_type',
|
'publication_type',
|
||||||
'uuid', # A UUID usually of type 4
|
'uuid', # A UUID usually of type 4
|
||||||
'language', # the primary language of this book
|
'languages', # ordered list of languages in this publication
|
||||||
'languages', # ordered list
|
|
||||||
'publisher', # Simple string, no special semantics
|
'publisher', # Simple string, no special semantics
|
||||||
# Absolute path to image file encoded in filesystem_encoding
|
# Absolute path to image file encoded in filesystem_encoding
|
||||||
'cover',
|
'cover',
|
||||||
@ -109,7 +108,7 @@ STANDARD_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union(
|
|||||||
# Metadata fields that smart update must do special processing to copy.
|
# Metadata fields that smart update must do special processing to copy.
|
||||||
SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors',
|
SC_FIELDS_NOT_COPIED = frozenset(['title', 'title_sort', 'authors',
|
||||||
'author_sort', 'author_sort_map',
|
'author_sort', 'author_sort_map',
|
||||||
'cover_data', 'tags', 'language',
|
'cover_data', 'tags', 'languages',
|
||||||
'identifiers'])
|
'identifiers'])
|
||||||
|
|
||||||
# Metadata fields that smart update should copy only if the source is not None
|
# Metadata fields that smart update should copy only if the source is not None
|
||||||
|
@ -102,6 +102,7 @@ class Metadata(object):
|
|||||||
@param other: None or a metadata object
|
@param other: None or a metadata object
|
||||||
'''
|
'''
|
||||||
_data = copy.deepcopy(NULL_VALUES)
|
_data = copy.deepcopy(NULL_VALUES)
|
||||||
|
_data.pop('language')
|
||||||
object.__setattr__(self, '_data', _data)
|
object.__setattr__(self, '_data', _data)
|
||||||
if other is not None:
|
if other is not None:
|
||||||
self.smart_update(other)
|
self.smart_update(other)
|
||||||
@ -136,6 +137,11 @@ class Metadata(object):
|
|||||||
_data = object.__getattribute__(self, '_data')
|
_data = object.__getattribute__(self, '_data')
|
||||||
if field in TOP_LEVEL_IDENTIFIERS:
|
if field in TOP_LEVEL_IDENTIFIERS:
|
||||||
return _data.get('identifiers').get(field, None)
|
return _data.get('identifiers').get(field, None)
|
||||||
|
if field == 'language':
|
||||||
|
try:
|
||||||
|
return _data.get('languages', [])[0]
|
||||||
|
except:
|
||||||
|
return NULL_VALUES['language']
|
||||||
if field in STANDARD_METADATA_FIELDS:
|
if field in STANDARD_METADATA_FIELDS:
|
||||||
return _data.get(field, None)
|
return _data.get(field, None)
|
||||||
try:
|
try:
|
||||||
@ -175,6 +181,11 @@ class Metadata(object):
|
|||||||
if not val:
|
if not val:
|
||||||
val = copy.copy(NULL_VALUES.get('identifiers', None))
|
val = copy.copy(NULL_VALUES.get('identifiers', None))
|
||||||
self.set_identifiers(val)
|
self.set_identifiers(val)
|
||||||
|
elif field == 'language':
|
||||||
|
langs = []
|
||||||
|
if val and val.lower() != 'und':
|
||||||
|
langs = [val]
|
||||||
|
_data['languages'] = langs
|
||||||
elif field in STANDARD_METADATA_FIELDS:
|
elif field in STANDARD_METADATA_FIELDS:
|
||||||
if val is None:
|
if val is None:
|
||||||
val = copy.copy(NULL_VALUES.get(field, None))
|
val = copy.copy(NULL_VALUES.get(field, None))
|
||||||
@ -553,9 +564,9 @@ class Metadata(object):
|
|||||||
for attr in TOP_LEVEL_IDENTIFIERS:
|
for attr in TOP_LEVEL_IDENTIFIERS:
|
||||||
copy_not_none(self, other, attr)
|
copy_not_none(self, other, attr)
|
||||||
|
|
||||||
other_lang = getattr(other, 'language', None)
|
other_lang = getattr(other, 'languages', [])
|
||||||
if other_lang and other_lang.lower() != 'und':
|
if other_lang and other_lang != ['und']:
|
||||||
self.language = other_lang
|
self.languages = list(other_lang)
|
||||||
if not getattr(self, 'series', None):
|
if not getattr(self, 'series', None):
|
||||||
self.series_index = None
|
self.series_index = None
|
||||||
|
|
||||||
@ -706,8 +717,8 @@ class Metadata(object):
|
|||||||
fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
|
fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
|
||||||
if self.series:
|
if self.series:
|
||||||
fmt('Series', self.series + ' #%s'%self.format_series_index())
|
fmt('Series', self.series + ' #%s'%self.format_series_index())
|
||||||
if not self.is_null('language'):
|
if not self.is_null('languages'):
|
||||||
fmt('Language', self.language)
|
fmt('Languages', ', '.join(self.languages))
|
||||||
if self.rating is not None:
|
if self.rating is not None:
|
||||||
fmt('Rating', self.rating)
|
fmt('Rating', self.rating)
|
||||||
if self.timestamp is not None:
|
if self.timestamp is not None:
|
||||||
@ -719,6 +730,8 @@ class Metadata(object):
|
|||||||
if self.identifiers:
|
if self.identifiers:
|
||||||
fmt('Identifiers', u', '.join(['%s:%s'%(k, v) for k, v in
|
fmt('Identifiers', u', '.join(['%s:%s'%(k, v) for k, v in
|
||||||
self.identifiers.iteritems()]))
|
self.identifiers.iteritems()]))
|
||||||
|
if self.languages:
|
||||||
|
fmt('Languages', u', '.join(self.languages))
|
||||||
if self.comments:
|
if self.comments:
|
||||||
fmt('Comments', self.comments)
|
fmt('Comments', self.comments)
|
||||||
|
|
||||||
@ -743,7 +756,7 @@ class Metadata(object):
|
|||||||
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
|
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
|
||||||
if self.series:
|
if self.series:
|
||||||
ans += [(_('Series'), unicode(self.series) + ' #%s'%self.format_series_index())]
|
ans += [(_('Series'), unicode(self.series) + ' #%s'%self.format_series_index())]
|
||||||
ans += [(_('Language'), unicode(self.language))]
|
ans += [(_('Languages'), u', '.join(self.languages))]
|
||||||
if self.timestamp is not None:
|
if self.timestamp is not None:
|
||||||
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
|
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
|
||||||
if self.pubdate is not None:
|
if self.pubdate is not None:
|
||||||
|
@ -19,7 +19,7 @@ from calibre.ebooks.metadata.toc import TOC
|
|||||||
from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn
|
from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.utils.date import parse_date, isoformat
|
from calibre.utils.date import parse_date, isoformat
|
||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang, canonicalize_lang
|
||||||
from calibre import prints, guess_type
|
from calibre import prints, guess_type
|
||||||
from calibre.utils.cleantext import clean_ascii_chars
|
from calibre.utils.cleantext import clean_ascii_chars
|
||||||
from calibre.utils.config import tweaks
|
from calibre.utils.config import tweaks
|
||||||
@ -515,6 +515,7 @@ class OPF(object): # {{{
|
|||||||
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
|
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
|
||||||
uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
|
||||||
'(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]')
|
'(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]')
|
||||||
|
languages_path = XPath('descendant::*[local-name()="language"]')
|
||||||
|
|
||||||
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
|
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
|
||||||
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
|
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
|
||||||
@ -523,7 +524,6 @@ class OPF(object): # {{{
|
|||||||
|
|
||||||
title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x))
|
title = MetadataField('title', formatter=lambda x: re.sub(r'\s+', ' ', x))
|
||||||
publisher = MetadataField('publisher')
|
publisher = MetadataField('publisher')
|
||||||
language = MetadataField('language')
|
|
||||||
comments = MetadataField('description')
|
comments = MetadataField('description')
|
||||||
category = MetadataField('type')
|
category = MetadataField('type')
|
||||||
rights = MetadataField('rights')
|
rights = MetadataField('rights')
|
||||||
@ -930,6 +930,44 @@ class OPF(object): # {{{
|
|||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def language(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
ans = self.languages
|
||||||
|
if ans:
|
||||||
|
return ans[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.languages = [val]
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def languages(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
ans = []
|
||||||
|
for match in self.languages_path(self.metadata):
|
||||||
|
t = self.get_text(match)
|
||||||
|
if t and t.strip():
|
||||||
|
l = canonicalize_lang(t.strip())
|
||||||
|
if l:
|
||||||
|
ans.append(l)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
matches = self.languages_path(self.metadata)
|
||||||
|
for x in matches:
|
||||||
|
x.getparent().remove(x)
|
||||||
|
|
||||||
|
for lang in val:
|
||||||
|
l = self.create_metadata_element('language')
|
||||||
|
self.set_text(l, unicode(lang))
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
def book_producer(self):
|
def book_producer(self):
|
||||||
@ -1052,9 +1090,9 @@ class OPF(object): # {{{
|
|||||||
val = getattr(mi, attr, None)
|
val = getattr(mi, attr, None)
|
||||||
if val is not None and val != [] and val != (None, None):
|
if val is not None and val != [] and val != (None, None):
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
lang = getattr(mi, 'language', None)
|
langs = getattr(mi, 'languages', [])
|
||||||
if lang and lang != 'und':
|
if langs and langs != ['und']:
|
||||||
self.language = lang
|
self.languages = langs
|
||||||
temp = self.to_book_metadata()
|
temp = self.to_book_metadata()
|
||||||
temp.smart_update(mi, replace_metadata=replace_metadata)
|
temp.smart_update(mi, replace_metadata=replace_metadata)
|
||||||
self._user_metadata_ = temp.get_all_user_metadata(True)
|
self._user_metadata_ = temp.get_all_user_metadata(True)
|
||||||
@ -1202,9 +1240,10 @@ class OPFCreator(Metadata):
|
|||||||
dc_attrs={'id':__appname__+'_id'}))
|
dc_attrs={'id':__appname__+'_id'}))
|
||||||
if getattr(self, 'pubdate', None) is not None:
|
if getattr(self, 'pubdate', None) is not None:
|
||||||
a(DC_ELEM('date', self.pubdate.isoformat()))
|
a(DC_ELEM('date', self.pubdate.isoformat()))
|
||||||
lang = self.language
|
langs = self.languages
|
||||||
if not lang or lang.lower() == 'und':
|
if not langs or langs == ['und']:
|
||||||
lang = get_lang().replace('_', '-')
|
langs = [get_lang().replace('_', '-').partition('-')[0]]
|
||||||
|
for lang in langs:
|
||||||
a(DC_ELEM('language', lang))
|
a(DC_ELEM('language', lang))
|
||||||
if self.comments:
|
if self.comments:
|
||||||
a(DC_ELEM('description', self.comments))
|
a(DC_ELEM('description', self.comments))
|
||||||
@ -1288,8 +1327,8 @@ def metadata_to_opf(mi, as_string=True):
|
|||||||
mi.book_producer = __appname__ + ' (%s) '%__version__ + \
|
mi.book_producer = __appname__ + ' (%s) '%__version__ + \
|
||||||
'[http://calibre-ebook.com]'
|
'[http://calibre-ebook.com]'
|
||||||
|
|
||||||
if not mi.language:
|
if not mi.languages:
|
||||||
mi.language = 'UND'
|
mi.languages = ['UND']
|
||||||
|
|
||||||
root = etree.fromstring(textwrap.dedent(
|
root = etree.fromstring(textwrap.dedent(
|
||||||
'''
|
'''
|
||||||
@ -1339,8 +1378,10 @@ def metadata_to_opf(mi, as_string=True):
|
|||||||
factory(DC('identifier'), val, scheme=icu_upper(key))
|
factory(DC('identifier'), val, scheme=icu_upper(key))
|
||||||
if mi.rights:
|
if mi.rights:
|
||||||
factory(DC('rights'), mi.rights)
|
factory(DC('rights'), mi.rights)
|
||||||
factory(DC('language'), mi.language if mi.language and mi.language.lower()
|
for lang in mi.languages:
|
||||||
!= 'und' else get_lang().replace('_', '-'))
|
if not lang or lang.lower() == 'und':
|
||||||
|
lang = get_lang().replace('_', '-').partition('-')[0]
|
||||||
|
factory(DC('language'), lang)
|
||||||
if mi.tags:
|
if mi.tags:
|
||||||
for tag in mi.tags:
|
for tag in mi.tags:
|
||||||
factory(DC('subject'), tag)
|
factory(DC('subject'), tag)
|
||||||
|
@ -94,7 +94,7 @@ gprefs.defaults['book_display_fields'] = [
|
|||||||
('path', True), ('publisher', False), ('rating', False),
|
('path', True), ('publisher', False), ('rating', False),
|
||||||
('author_sort', False), ('sort', False), ('timestamp', False),
|
('author_sort', False), ('sort', False), ('timestamp', False),
|
||||||
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
('uuid', False), ('comments', True), ('id', False), ('pubdate', False),
|
||||||
('last_modified', False), ('size', False),
|
('last_modified', False), ('size', False), ('languages', False),
|
||||||
]
|
]
|
||||||
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
|
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
|
||||||
gprefs.defaults['preserve_date_on_ctl'] = True
|
gprefs.defaults['preserve_date_on_ctl'] = True
|
||||||
|
@ -24,6 +24,7 @@ from calibre.gui2 import (config, open_local_file, open_url, pixmap_to_data,
|
|||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.utils.formatter import EvalFormatter
|
from calibre.utils.formatter import EvalFormatter
|
||||||
from calibre.utils.date import is_date_undefined
|
from calibre.utils.date import is_date_undefined
|
||||||
|
from calibre.utils.localization import calibre_langcode_to_name
|
||||||
|
|
||||||
def render_html(mi, css, vertical, widget, all_fields=False): # {{{
|
def render_html(mi, css, vertical, widget, all_fields=False): # {{{
|
||||||
table = render_data(mi, all_fields=all_fields,
|
table = render_data(mi, all_fields=all_fields,
|
||||||
@ -152,6 +153,12 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
|
|||||||
authors.append(aut)
|
authors.append(aut)
|
||||||
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
|
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
|
||||||
u' & '.join(authors))))
|
u' & '.join(authors))))
|
||||||
|
elif field == 'languages':
|
||||||
|
if not mi.languages:
|
||||||
|
continue
|
||||||
|
names = filter(None, map(calibre_langcode_to_name, mi.languages))
|
||||||
|
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name,
|
||||||
|
u', '.join(names))))
|
||||||
else:
|
else:
|
||||||
val = mi.format_field(field)[-1]
|
val = mi.format_field(field)[-1]
|
||||||
if val is None:
|
if val is None:
|
||||||
|
@ -134,7 +134,7 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart, \
|
do_remove_conv, do_auto_author, series, do_series_restart, \
|
||||||
series_start_value, do_title_case, cover_action, clear_series, \
|
series_start_value, do_title_case, cover_action, clear_series, \
|
||||||
pubdate, adddate, do_title_sort = self.args
|
pubdate, adddate, do_title_sort, languages, clear_languages = self.args
|
||||||
|
|
||||||
|
|
||||||
# first loop: do author and title. These will commit at the end of each
|
# first loop: do author and title. These will commit at the end of each
|
||||||
@ -238,6 +238,12 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
|
|
||||||
if do_remove_conv:
|
if do_remove_conv:
|
||||||
self.db.delete_conversion_options(id, 'PIPE', commit=False)
|
self.db.delete_conversion_options(id, 'PIPE', commit=False)
|
||||||
|
|
||||||
|
if clear_languages:
|
||||||
|
self.db.set_languages(id, [], notify=False, commit=False)
|
||||||
|
elif languages:
|
||||||
|
self.db.set_languages(id, languages, notify=False, commit=False)
|
||||||
|
|
||||||
elif self.current_phase == 3:
|
elif self.current_phase == 3:
|
||||||
# both of these are fast enough to just do them all
|
# both of these are fast enough to just do them all
|
||||||
for w in self.cc_widgets:
|
for w in self.cc_widgets:
|
||||||
@ -329,6 +335,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
geom = gprefs.get('bulk_metadata_window_geometry', None)
|
geom = gprefs.get('bulk_metadata_window_geometry', None)
|
||||||
if geom is not None:
|
if geom is not None:
|
||||||
self.restoreGeometry(bytes(geom))
|
self.restoreGeometry(bytes(geom))
|
||||||
|
self.languages.setEditText('')
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def save_state(self, *args):
|
def save_state(self, *args):
|
||||||
@ -352,6 +359,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.do_again = True
|
self.do_again = True
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
# S&R {{{
|
||||||
def prepare_search_and_replace(self):
|
def prepare_search_and_replace(self):
|
||||||
self.search_for.initialize('bulk_edit_search_for')
|
self.search_for.initialize('bulk_edit_search_for')
|
||||||
self.replace_with.initialize('bulk_edit_replace_with')
|
self.replace_with.initialize('bulk_edit_replace_with')
|
||||||
@ -796,6 +804,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
# permanent. Make sure it really is.
|
# permanent. Make sure it really is.
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.model.refresh_ids(list(books_to_refresh))
|
self.model.refresh_ids(list(books_to_refresh))
|
||||||
|
# }}}
|
||||||
|
|
||||||
def create_custom_column_editors(self):
|
def create_custom_column_editors(self):
|
||||||
w = self.central_widget.widget(1)
|
w = self.central_widget.widget(1)
|
||||||
@ -919,6 +928,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_auto_author = self.auto_author_sort.isChecked()
|
do_auto_author = self.auto_author_sort.isChecked()
|
||||||
do_title_case = self.change_title_to_title_case.isChecked()
|
do_title_case = self.change_title_to_title_case.isChecked()
|
||||||
do_title_sort = self.update_title_sort.isChecked()
|
do_title_sort = self.update_title_sort.isChecked()
|
||||||
|
clear_languages = self.clear_languages.isChecked()
|
||||||
|
languages = self.languages.lang_codes
|
||||||
pubdate = adddate = None
|
pubdate = adddate = None
|
||||||
if self.apply_pubdate.isChecked():
|
if self.apply_pubdate.isChecked():
|
||||||
pubdate = qt_to_dt(self.pubdate.date())
|
pubdate = qt_to_dt(self.pubdate.date())
|
||||||
@ -937,7 +948,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart,
|
do_remove_conv, do_auto_author, series, do_series_restart,
|
||||||
series_start_value, do_title_case, cover_action, clear_series,
|
series_start_value, do_title_case, cover_action, clear_series,
|
||||||
pubdate, adddate, do_title_sort)
|
pubdate, adddate, do_title_sort, languages, clear_languages)
|
||||||
|
|
||||||
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
||||||
%len(self.ids), args, self.db, self.ids,
|
%len(self.ids), args, self.db, self.ids,
|
||||||
|
@ -443,7 +443,7 @@ from the value in the box</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="11" column="0">
|
<item row="13" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove &format:</string>
|
<string>Remove &format:</string>
|
||||||
@ -453,7 +453,7 @@ from the value in the box</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="11" column="1">
|
<item row="13" column="1">
|
||||||
<widget class="QComboBox" name="remove_format">
|
<widget class="QComboBox" name="remove_format">
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
@ -463,7 +463,7 @@ from the value in the box</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="12" column="0">
|
<item row="14" column="0">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -479,7 +479,7 @@ from the value in the box</string>
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="13" column="0" colspan="3">
|
<item row="15" column="0" colspan="3">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="change_title_to_title_case">
|
<widget class="QCheckBox" name="change_title_to_title_case">
|
||||||
@ -529,7 +529,7 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="14" column="0" colspan="3">
|
<item row="16" column="0" colspan="3">
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Change &cover</string>
|
<string>Change &cover</string>
|
||||||
@ -559,7 +559,7 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="15" column="0">
|
<item row="17" column="0">
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -572,6 +572,29 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="11" column="0">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Languages:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>languages</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="1">
|
||||||
|
<widget class="LanguagesEdit" name="languages"/>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="2">
|
||||||
|
<widget class="QCheckBox" name="clear_languages">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove &all</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab">
|
<widget class="QWidget" name="tab">
|
||||||
@ -1145,6 +1168,11 @@ not multiple and the destination field is multiple</string>
|
|||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>LanguagesEdit</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>calibre/gui2/languages.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>authors</tabstop>
|
<tabstop>authors</tabstop>
|
||||||
|
62
src/calibre/gui2/languages.py
Normal file
62
src/calibre/gui2/languages.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#!/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 calibre.gui2.complete import MultiCompleteComboBox
|
||||||
|
from calibre.utils.localization import lang_map
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
|
class LanguagesEdit(MultiCompleteComboBox):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
MultiCompleteComboBox.__init__(self, parent)
|
||||||
|
|
||||||
|
self._lang_map = lang_map()
|
||||||
|
self._rmap = {v:k for k,v in self._lang_map.iteritems()}
|
||||||
|
|
||||||
|
all_items = sorted(self._lang_map.itervalues(),
|
||||||
|
key=sort_key)
|
||||||
|
self.update_items_cache(all_items)
|
||||||
|
for item in all_items:
|
||||||
|
self.addItem(item)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def lang_codes(self):
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
vals = [x.strip() for x in
|
||||||
|
unicode(self.lineEdit().text()).split(',')]
|
||||||
|
ans = []
|
||||||
|
for name in vals:
|
||||||
|
if name:
|
||||||
|
code = self._rmap.get(name, None)
|
||||||
|
if code is not None:
|
||||||
|
ans.append(code)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def fset(self, lang_codes):
|
||||||
|
ans = []
|
||||||
|
for lc in lang_codes:
|
||||||
|
name = self._lang_map.get(lc, None)
|
||||||
|
if name is not None:
|
||||||
|
ans.append(name)
|
||||||
|
self.setEditText(', '.join(ans))
|
||||||
|
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
vals = [x.strip() for x in
|
||||||
|
unicode(self.lineEdit().text()).split(',')]
|
||||||
|
bad = []
|
||||||
|
for name in vals:
|
||||||
|
if name:
|
||||||
|
code = self._rmap.get(name, None)
|
||||||
|
if code is None:
|
||||||
|
bad.append(name)
|
||||||
|
return bad
|
||||||
|
|
@ -23,6 +23,7 @@ from calibre.utils.formatter import validation_formatter
|
|||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
|
from calibre.gui2.dialogs.comments_dialog import CommentsDialog
|
||||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||||
|
from calibre.gui2.languages import LanguagesEdit
|
||||||
|
|
||||||
|
|
||||||
class RatingDelegate(QStyledItemDelegate): # {{{
|
class RatingDelegate(QStyledItemDelegate): # {{{
|
||||||
@ -155,7 +156,7 @@ class TextDelegate(QStyledItemDelegate): # {{{
|
|||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
'''
|
'''
|
||||||
Delegate for text data. If auto_complete_function needs to return a list
|
Delegate for text data. If auto_complete_function needs to return a list
|
||||||
of text items to auto-complete with. The funciton is None no
|
of text items to auto-complete with. If the function is None no
|
||||||
auto-complete will be used.
|
auto-complete will be used.
|
||||||
'''
|
'''
|
||||||
QStyledItemDelegate.__init__(self, parent)
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
@ -229,6 +230,20 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
|
|||||||
QStyledItemDelegate.setModelData(self, editor, model, index)
|
QStyledItemDelegate.setModelData(self, editor, model, index)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class LanguagesDelegate(QStyledItemDelegate): # {{{
|
||||||
|
|
||||||
|
def createEditor(self, parent, option, index):
|
||||||
|
editor = LanguagesEdit(parent)
|
||||||
|
ct = index.data(Qt.DisplayRole).toString()
|
||||||
|
editor.setEditText(ct)
|
||||||
|
editor.lineEdit().selectAll()
|
||||||
|
return editor
|
||||||
|
|
||||||
|
def setModelData(self, editor, model, index):
|
||||||
|
val = ','.join(editor.lang_codes)
|
||||||
|
model.setData(index, QVariant(val), Qt.EditRole)
|
||||||
|
# }}}
|
||||||
|
|
||||||
class CcDateDelegate(QStyledItemDelegate): # {{{
|
class CcDateDelegate(QStyledItemDelegate): # {{{
|
||||||
'''
|
'''
|
||||||
Delegate for custom columns dates. Because this delegate stores the
|
Delegate for custom columns dates. Because this delegate stores the
|
||||||
|
@ -25,6 +25,7 @@ from calibre.library.caches import (_match, CONTAINS_MATCH, EQUALS_MATCH,
|
|||||||
from calibre import strftime, isbytestring
|
from calibre import strftime, isbytestring
|
||||||
from calibre.constants import filesystem_encoding, DEBUG
|
from calibre.constants import filesystem_encoding, DEBUG
|
||||||
from calibre.gui2.library import DEFAULT_SORT
|
from calibre.gui2.library import DEFAULT_SORT
|
||||||
|
from calibre.utils.localization import calibre_langcode_to_name
|
||||||
|
|
||||||
def human_readable(size, precision=1):
|
def human_readable(size, precision=1):
|
||||||
""" Convert a size in bytes into megabytes """
|
""" Convert a size in bytes into megabytes """
|
||||||
@ -64,6 +65,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
'tags' : _("Tags"),
|
'tags' : _("Tags"),
|
||||||
'series' : ngettext("Series", 'Series', 1),
|
'series' : ngettext("Series", 'Series', 1),
|
||||||
'last_modified' : _('Modified'),
|
'last_modified' : _('Modified'),
|
||||||
|
'languages' : _('Languages'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, parent=None, buffer=40):
|
def __init__(self, parent=None, buffer=40):
|
||||||
@ -71,7 +73,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.db = None
|
self.db = None
|
||||||
self.book_on_device = None
|
self.book_on_device = None
|
||||||
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
|
self.editable_cols = ['title', 'authors', 'rating', 'publisher',
|
||||||
'tags', 'series', 'timestamp', 'pubdate']
|
'tags', 'series', 'timestamp', 'pubdate',
|
||||||
|
'languages']
|
||||||
self.default_image = default_image()
|
self.default_image = default_image()
|
||||||
self.sorted_on = DEFAULT_SORT
|
self.sorted_on = DEFAULT_SORT
|
||||||
self.sort_history = [self.sorted_on]
|
self.sort_history = [self.sorted_on]
|
||||||
@ -540,6 +543,13 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def languages(r, idx=-1):
|
||||||
|
lc = self.db.data[r][idx]
|
||||||
|
if lc:
|
||||||
|
langs = [calibre_langcode_to_name(l.strip()) for l in lc.split(',')]
|
||||||
|
return QVariant(', '.join(langs))
|
||||||
|
return None
|
||||||
|
|
||||||
def tags(r, idx=-1):
|
def tags(r, idx=-1):
|
||||||
tags = self.db.data[r][idx]
|
tags = self.db.data[r][idx]
|
||||||
if tags:
|
if tags:
|
||||||
@ -641,6 +651,8 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
siix=self.db.field_metadata['series_index']['rec_index']),
|
siix=self.db.field_metadata['series_index']['rec_index']),
|
||||||
'ondevice' : functools.partial(text_type,
|
'ondevice' : functools.partial(text_type,
|
||||||
idx=self.db.field_metadata['ondevice']['rec_index'], mult=None),
|
idx=self.db.field_metadata['ondevice']['rec_index'], mult=None),
|
||||||
|
'languages': functools.partial(languages,
|
||||||
|
idx=self.db.field_metadata['languages']['rec_index']),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dc_decorator = {
|
self.dc_decorator = {
|
||||||
@ -884,6 +896,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
if val.isNull() or not val.isValid():
|
if val.isNull() or not val.isValid():
|
||||||
return False
|
return False
|
||||||
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
|
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
|
||||||
|
elif column == 'languages':
|
||||||
|
val = val.split(',')
|
||||||
|
self.db.set_languages(id, val)
|
||||||
else:
|
else:
|
||||||
books_to_refresh |= self.db.set(row, column, val,
|
books_to_refresh |= self.db.set(row, column, val,
|
||||||
allow_case_change=True)
|
allow_case_change=True)
|
||||||
|
@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import os
|
import os
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
|
from PyQt4.Qt import (QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal,
|
||||||
QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication, \
|
QModelIndex, QIcon, QItemSelection, QMimeData, QDrag, QApplication,
|
||||||
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect
|
QPoint, QPixmap, QUrl, QImage, QPainter, QColor, QRect)
|
||||||
|
|
||||||
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
|
from calibre.gui2.library.delegates import (RatingDelegate, PubDateDelegate,
|
||||||
TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, \
|
TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate,
|
||||||
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, \
|
CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate,
|
||||||
CcEnumDelegate, CcNumberDelegate
|
CcEnumDelegate, CcNumberDelegate, LanguagesDelegate)
|
||||||
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
from calibre.gui2.library.models import BooksModel, DeviceBooksModel
|
||||||
from calibre.utils.config import tweaks, prefs
|
from calibre.utils.config import tweaks, prefs
|
||||||
from calibre.gui2 import error_dialog, gprefs
|
from calibre.gui2 import error_dialog, gprefs
|
||||||
@ -85,6 +85,7 @@ class BooksView(QTableView): # {{{
|
|||||||
self.pubdate_delegate = PubDateDelegate(self)
|
self.pubdate_delegate = PubDateDelegate(self)
|
||||||
self.last_modified_delegate = DateDelegate(self,
|
self.last_modified_delegate = DateDelegate(self,
|
||||||
tweak_name='gui_last_modified_display_format')
|
tweak_name='gui_last_modified_display_format')
|
||||||
|
self.languages_delegate = LanguagesDelegate(self)
|
||||||
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
|
self.tags_delegate = CompleteDelegate(self, ',', 'all_tags')
|
||||||
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
|
self.authors_delegate = CompleteDelegate(self, '&', 'all_author_names', True)
|
||||||
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
|
self.cc_names_delegate = CompleteDelegate(self, '&', 'all_custom', True)
|
||||||
@ -306,6 +307,7 @@ class BooksView(QTableView): # {{{
|
|||||||
state['hidden_columns'] = [cm[i] for i in range(h.count())
|
state['hidden_columns'] = [cm[i] for i in range(h.count())
|
||||||
if h.isSectionHidden(i) and cm[i] != 'ondevice']
|
if h.isSectionHidden(i) and cm[i] != 'ondevice']
|
||||||
state['last_modified_injected'] = True
|
state['last_modified_injected'] = True
|
||||||
|
state['languages_injected'] = True
|
||||||
state['sort_history'] = \
|
state['sort_history'] = \
|
||||||
self.cleanup_sort_history(self.model().sort_history)
|
self.cleanup_sort_history(self.model().sort_history)
|
||||||
state['column_positions'] = {}
|
state['column_positions'] = {}
|
||||||
@ -390,7 +392,7 @@ class BooksView(QTableView): # {{{
|
|||||||
|
|
||||||
def get_default_state(self):
|
def get_default_state(self):
|
||||||
old_state = {
|
old_state = {
|
||||||
'hidden_columns': ['last_modified'],
|
'hidden_columns': ['last_modified', 'languages'],
|
||||||
'sort_history':[DEFAULT_SORT],
|
'sort_history':[DEFAULT_SORT],
|
||||||
'column_positions': {},
|
'column_positions': {},
|
||||||
'column_sizes': {},
|
'column_sizes': {},
|
||||||
@ -399,6 +401,7 @@ class BooksView(QTableView): # {{{
|
|||||||
'timestamp':'center',
|
'timestamp':'center',
|
||||||
'pubdate':'center'},
|
'pubdate':'center'},
|
||||||
'last_modified_injected': True,
|
'last_modified_injected': True,
|
||||||
|
'languages_injected': True,
|
||||||
}
|
}
|
||||||
h = self.column_header
|
h = self.column_header
|
||||||
cm = self.column_map
|
cm = self.column_map
|
||||||
@ -430,11 +433,20 @@ class BooksView(QTableView): # {{{
|
|||||||
if ans is not None:
|
if ans is not None:
|
||||||
db.prefs[name] = ans
|
db.prefs[name] = ans
|
||||||
else:
|
else:
|
||||||
|
injected = False
|
||||||
if not ans.get('last_modified_injected', False):
|
if not ans.get('last_modified_injected', False):
|
||||||
|
injected = True
|
||||||
ans['last_modified_injected'] = True
|
ans['last_modified_injected'] = True
|
||||||
hc = ans.get('hidden_columns', [])
|
hc = ans.get('hidden_columns', [])
|
||||||
if 'last_modified' not in hc:
|
if 'last_modified' not in hc:
|
||||||
hc.append('last_modified')
|
hc.append('last_modified')
|
||||||
|
if not ans.get('languages_injected', False):
|
||||||
|
injected = True
|
||||||
|
ans['languages_injected'] = True
|
||||||
|
hc = ans.get('hidden_columns', [])
|
||||||
|
if 'languages' not in hc:
|
||||||
|
hc.append('languages')
|
||||||
|
if injected:
|
||||||
db.prefs[name] = ans
|
db.prefs[name] = ans
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@ -501,7 +513,7 @@ class BooksView(QTableView): # {{{
|
|||||||
for i in range(self.model().columnCount(None)):
|
for i in range(self.model().columnCount(None)):
|
||||||
if self.itemDelegateForColumn(i) in (self.rating_delegate,
|
if self.itemDelegateForColumn(i) in (self.rating_delegate,
|
||||||
self.timestamp_delegate, self.pubdate_delegate,
|
self.timestamp_delegate, self.pubdate_delegate,
|
||||||
self.last_modified_delegate):
|
self.last_modified_delegate, self.languages_delegate):
|
||||||
self.setItemDelegateForColumn(i, self.itemDelegate())
|
self.setItemDelegateForColumn(i, self.itemDelegate())
|
||||||
|
|
||||||
cm = self.column_map
|
cm = self.column_map
|
||||||
|
@ -34,6 +34,7 @@ from calibre.library.comments import comments_to_html
|
|||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.utils.icu import strcmp
|
from calibre.utils.icu import strcmp
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
from calibre.gui2.languages import LanguagesEdit as LE
|
||||||
|
|
||||||
def save_dialog(parent, title, msg, det_msg=''):
|
def save_dialog(parent, title, msg, det_msg=''):
|
||||||
d = QMessageBox(parent)
|
d = QMessageBox(parent)
|
||||||
@ -1133,6 +1134,43 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class LanguagesEdit(LE): # {{{
|
||||||
|
|
||||||
|
LABEL = _('&Languages:')
|
||||||
|
TOOLTIP = _('A comma separated list of languages for this book')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
LE.__init__(self, *args, **kwargs)
|
||||||
|
self.setToolTip(self.TOOLTIP)
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def current_val(self):
|
||||||
|
def fget(self): return self.lang_codes
|
||||||
|
def fset(self, val): self.lang_codes = val
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def initialize(self, db, id_):
|
||||||
|
lc = []
|
||||||
|
langs = db.languages(id_, index_is_id=True)
|
||||||
|
if langs:
|
||||||
|
lc = [x.strip() for x in langs.split(',')]
|
||||||
|
self.current_val = self.original_val = lc
|
||||||
|
|
||||||
|
def commit(self, db, id_):
|
||||||
|
bad = self.validate()
|
||||||
|
if bad:
|
||||||
|
error_dialog(self, _('Unknown language'),
|
||||||
|
ngettext('The language %s is not recognized',
|
||||||
|
'The languages %s are not recognized', len(bad))%(
|
||||||
|
', '.join(bad)),
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
|
cv = self.current_val
|
||||||
|
if cv != self.original_val:
|
||||||
|
db.set_languages(id_, cv)
|
||||||
|
return True
|
||||||
|
# }}}
|
||||||
|
|
||||||
class IdentifiersEdit(QLineEdit): # {{{
|
class IdentifiersEdit(QLineEdit): # {{{
|
||||||
LABEL = _('I&ds:')
|
LABEL = _('I&ds:')
|
||||||
BASE_TT = _('Edit the identifiers for this book. '
|
BASE_TT = _('Edit the identifiers for this book. '
|
||||||
|
@ -20,7 +20,7 @@ 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, LanguagesEdit)
|
||||||
from calibre.gui2.metadata.single_download import FullFetch
|
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
|
||||||
@ -183,6 +183,9 @@ class MetadataSingleDialogBase(ResizableDialog):
|
|||||||
self.publisher = PublisherEdit(self)
|
self.publisher = PublisherEdit(self)
|
||||||
self.basic_metadata_widgets.append(self.publisher)
|
self.basic_metadata_widgets.append(self.publisher)
|
||||||
|
|
||||||
|
self.languages = LanguagesEdit(self)
|
||||||
|
self.basic_metadata_widgets.append(self.languages)
|
||||||
|
|
||||||
self.timestamp = DateEdit(self)
|
self.timestamp = DateEdit(self)
|
||||||
self.pubdate = PubdateEdit(self)
|
self.pubdate = PubdateEdit(self)
|
||||||
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
|
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
|
||||||
@ -610,11 +613,13 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
|
|||||||
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
create_row2(5, self.pubdate, self.pubdate.clear_button)
|
||||||
sto(self.pubdate.clear_button, self.publisher)
|
sto(self.pubdate.clear_button, self.publisher)
|
||||||
create_row2(6, self.publisher)
|
create_row2(6, self.publisher)
|
||||||
|
sto(self.publisher, self.languages)
|
||||||
|
create_row2(7, self.languages)
|
||||||
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
|
||||||
QSizePolicy.Expanding)
|
QSizePolicy.Expanding)
|
||||||
l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3)
|
l.addItem(self.tabs[0].spc_two, 9, 0, 1, 3)
|
||||||
l.addWidget(self.fetch_metadata_button, 9, 0, 1, 2)
|
l.addWidget(self.fetch_metadata_button, 10, 0, 1, 2)
|
||||||
l.addWidget(self.config_metadata_button, 9, 2, 1, 1)
|
l.addWidget(self.config_metadata_button, 10, 2, 1, 1)
|
||||||
|
|
||||||
self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self)
|
self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self)
|
||||||
gb.l = l = QVBoxLayout()
|
gb.l = l = QVBoxLayout()
|
||||||
@ -717,16 +722,17 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
|||||||
create_row(7, self.rating, self.pubdate)
|
create_row(7, self.rating, self.pubdate)
|
||||||
create_row(8, self.pubdate, self.publisher,
|
create_row(8, self.pubdate, self.publisher,
|
||||||
button=self.pubdate.clear_button, icon='trash.png')
|
button=self.pubdate.clear_button, icon='trash.png')
|
||||||
create_row(9, self.publisher, self.timestamp)
|
create_row(9, self.publisher, self.languages)
|
||||||
create_row(10, self.timestamp, self.identifiers,
|
create_row(10, self.languages, self.timestamp)
|
||||||
|
create_row(11, self.timestamp, self.identifiers,
|
||||||
button=self.timestamp.clear_button, icon='trash.png')
|
button=self.timestamp.clear_button, icon='trash.png')
|
||||||
create_row(11, self.identifiers, self.comments,
|
create_row(12, self.identifiers, self.comments,
|
||||||
button=self.clear_identifiers_button, icon='trash.png')
|
button=self.clear_identifiers_button, icon='trash.png')
|
||||||
sto(self.clear_identifiers_button, self.swap_title_author_button)
|
sto(self.clear_identifiers_button, self.swap_title_author_button)
|
||||||
sto(self.swap_title_author_button, self.manage_authors_button)
|
sto(self.swap_title_author_button, self.manage_authors_button)
|
||||||
sto(self.manage_authors_button, self.paste_isbn_button)
|
sto(self.manage_authors_button, self.paste_isbn_button)
|
||||||
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
||||||
12, 1, 1 ,1)
|
13, 1, 1 ,1)
|
||||||
|
|
||||||
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
||||||
if w is not None:
|
if w is not None:
|
||||||
@ -852,16 +858,17 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{
|
|||||||
create_row(7, self.rating, self.pubdate)
|
create_row(7, self.rating, self.pubdate)
|
||||||
create_row(8, self.pubdate, self.publisher,
|
create_row(8, self.pubdate, self.publisher,
|
||||||
button=self.pubdate.clear_button, icon='trash.png')
|
button=self.pubdate.clear_button, icon='trash.png')
|
||||||
create_row(9, self.publisher, self.timestamp)
|
create_row(9, self.publisher, self.languages)
|
||||||
create_row(10, self.timestamp, self.identifiers,
|
create_row(10, self.languages, self.timestamp)
|
||||||
|
create_row(11, self.timestamp, self.identifiers,
|
||||||
button=self.timestamp.clear_button, icon='trash.png')
|
button=self.timestamp.clear_button, icon='trash.png')
|
||||||
create_row(11, self.identifiers, self.comments,
|
create_row(12, self.identifiers, self.comments,
|
||||||
button=self.clear_identifiers_button, icon='trash.png')
|
button=self.clear_identifiers_button, icon='trash.png')
|
||||||
sto(self.clear_identifiers_button, self.swap_title_author_button)
|
sto(self.clear_identifiers_button, self.swap_title_author_button)
|
||||||
sto(self.swap_title_author_button, self.manage_authors_button)
|
sto(self.swap_title_author_button, self.manage_authors_button)
|
||||||
sto(self.manage_authors_button, self.paste_isbn_button)
|
sto(self.manage_authors_button, self.paste_isbn_button)
|
||||||
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
|
||||||
12, 1, 1 ,1)
|
13, 1, 1 ,1)
|
||||||
|
|
||||||
# Custom metadata in col 1
|
# Custom metadata in col 1
|
||||||
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
||||||
|
@ -15,6 +15,7 @@ from calibre.utils.config import tweaks, prefs
|
|||||||
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
|
from calibre.utils.localization import canonicalize_lang
|
||||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
@ -721,9 +722,13 @@ class ResultCache(SearchQueryParser): # {{{
|
|||||||
if loc == db_col['authors']:
|
if loc == db_col['authors']:
|
||||||
### DB stores authors with commas changed to bars, so change query
|
### DB stores authors with commas changed to bars, so change query
|
||||||
if matchkind == REGEXP_MATCH:
|
if matchkind == REGEXP_MATCH:
|
||||||
q = query.replace(',', r'\|');
|
q = query.replace(',', r'\|')
|
||||||
else:
|
else:
|
||||||
q = query.replace(',', '|');
|
q = query.replace(',', '|')
|
||||||
|
elif loc == db_col['languages']:
|
||||||
|
q = canonicalize_lang(query)
|
||||||
|
if q is None:
|
||||||
|
q = query
|
||||||
else:
|
else:
|
||||||
q = query
|
q = query
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ from calibre.utils.magick.draw import save_cover_data_to
|
|||||||
from calibre.utils.recycle_bin import delete_file, delete_tree
|
from calibre.utils.recycle_bin import delete_file, delete_tree
|
||||||
from calibre.utils.formatter_functions import load_user_template_functions
|
from calibre.utils.formatter_functions import load_user_template_functions
|
||||||
from calibre.db.errors import NoSuchFormat
|
from calibre.db.errors import NoSuchFormat
|
||||||
|
from calibre.utils.localization import (canonicalize_lang,
|
||||||
|
calibre_langcode_to_name)
|
||||||
|
|
||||||
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
||||||
SPOOL_SIZE = 30*1024*1024
|
SPOOL_SIZE = 30*1024*1024
|
||||||
@ -372,6 +374,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'aum_sortconcat(link.id, authors.name, authors.sort, authors.link)'),
|
'aum_sortconcat(link.id, authors.name, authors.sort, authors.link)'),
|
||||||
'last_modified',
|
'last_modified',
|
||||||
'(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers',
|
'(SELECT identifiers_concat(type, val) FROM identifiers WHERE identifiers.book=books.id) identifiers',
|
||||||
|
('languages', 'languages', 'lang_code',
|
||||||
|
'sortconcat(link.id, languages.lang_code)'),
|
||||||
]
|
]
|
||||||
lines = []
|
lines = []
|
||||||
for col in columns:
|
for col in columns:
|
||||||
@ -390,7 +394,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'size':4, 'rating':5, 'tags':6, 'comments':7, 'series':8,
|
'size':4, 'rating':5, 'tags':6, 'comments':7, 'series':8,
|
||||||
'publisher':9, 'series_index':10, 'sort':11, 'author_sort':12,
|
'publisher':9, 'series_index':10, 'sort':11, 'author_sort':12,
|
||||||
'formats':13, 'path':14, 'pubdate':15, 'uuid':16, 'cover':17,
|
'formats':13, 'path':14, 'pubdate':15, 'uuid':16, 'cover':17,
|
||||||
'au_map':18, 'last_modified':19, 'identifiers':20}
|
'au_map':18, 'last_modified':19, 'identifiers':20, 'languages':21}
|
||||||
|
|
||||||
for k,v in self.FIELD_MAP.iteritems():
|
for k,v in self.FIELD_MAP.iteritems():
|
||||||
self.field_metadata.set_field_record_index(k, v, prefer_custom=False)
|
self.field_metadata.set_field_record_index(k, v, prefer_custom=False)
|
||||||
@ -469,7 +473,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'author_sort', 'authors', 'comment', 'comments',
|
'author_sort', 'authors', 'comment', 'comments',
|
||||||
'publisher', 'rating', 'series', 'series_index', 'tags',
|
'publisher', 'rating', 'series', 'series_index', 'tags',
|
||||||
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice',
|
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice',
|
||||||
'metadata_last_modified',
|
'metadata_last_modified', 'languages',
|
||||||
):
|
):
|
||||||
fm = {'comment':'comments', 'metadata_last_modified':
|
fm = {'comment':'comments', 'metadata_last_modified':
|
||||||
'last_modified'}.get(prop, prop)
|
'last_modified'}.get(prop, prop)
|
||||||
@ -930,6 +934,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
tags = row[fm['tags']]
|
tags = row[fm['tags']]
|
||||||
if tags:
|
if tags:
|
||||||
mi.tags = [i.strip() for i in tags.split(',')]
|
mi.tags = [i.strip() for i in tags.split(',')]
|
||||||
|
languages = row[fm['languages']]
|
||||||
|
if languages:
|
||||||
|
mi.languages = [i.strip() for i in languages.split(',')]
|
||||||
mi.series = row[fm['series']]
|
mi.series = row[fm['series']]
|
||||||
if mi.series:
|
if mi.series:
|
||||||
mi.series_index = row[fm['series_index']]
|
mi.series_index = row[fm['series_index']]
|
||||||
@ -1390,7 +1397,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
('authors', 'authors', 'author'),
|
('authors', 'authors', 'author'),
|
||||||
('publishers', 'publishers', 'publisher'),
|
('publishers', 'publishers', 'publisher'),
|
||||||
('tags', 'tags', 'tag'),
|
('tags', 'tags', 'tag'),
|
||||||
('series', 'series', 'series')
|
('series', 'series', 'series'),
|
||||||
|
('languages', 'languages', 'lang_code'),
|
||||||
]:
|
]:
|
||||||
doit(ltable, table, ltable_col)
|
doit(ltable, table, ltable_col)
|
||||||
|
|
||||||
@ -1507,6 +1515,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'series' : self.get_series_with_ids,
|
'series' : self.get_series_with_ids,
|
||||||
'publisher': self.get_publishers_with_ids,
|
'publisher': self.get_publishers_with_ids,
|
||||||
'tags' : self.get_tags_with_ids,
|
'tags' : self.get_tags_with_ids,
|
||||||
|
'languages': self.get_languages_with_ids,
|
||||||
'rating' : self.get_ratings_with_ids,
|
'rating' : self.get_ratings_with_ids,
|
||||||
}
|
}
|
||||||
func = funcs.get(category, None)
|
func = funcs.get(category, None)
|
||||||
@ -1521,6 +1530,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
for l in list:
|
for l in list:
|
||||||
(id, val, sort_val) = (l[0], l[1], l[2])
|
(id, val, sort_val) = (l[0], l[1], l[2])
|
||||||
tids[category][val] = (id, sort_val)
|
tids[category][val] = (id, sort_val)
|
||||||
|
elif category == 'languages':
|
||||||
|
for l in list:
|
||||||
|
id, val = l[0], calibre_langcode_to_name(l[1])
|
||||||
|
tids[category][l[1]] = (id, val)
|
||||||
elif cat['datatype'] == 'series':
|
elif cat['datatype'] == 'series':
|
||||||
for l in list:
|
for l in list:
|
||||||
(id, val) = (l[0], l[1])
|
(id, val) = (l[0], l[1])
|
||||||
@ -1620,6 +1633,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
item.rt += rating
|
item.rt += rating
|
||||||
item.rc += 1
|
item.rc += 1
|
||||||
except:
|
except:
|
||||||
|
prints(tid_cat, val)
|
||||||
prints('get_categories: item', val, 'is not in', cat, 'list!')
|
prints('get_categories: item', val, 'is not in', cat, 'list!')
|
||||||
|
|
||||||
#print 'end phase "books":', time.clock() - last, 'seconds'
|
#print 'end phase "books":', time.clock() - last, 'seconds'
|
||||||
@ -1684,6 +1698,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
# Clean up the authors strings to human-readable form
|
# Clean up the authors strings to human-readable form
|
||||||
formatter = (lambda x: x.replace('|', ','))
|
formatter = (lambda x: x.replace('|', ','))
|
||||||
items = [v for v in tcategories[category].values() if v.c > 0]
|
items = [v for v in tcategories[category].values() if v.c > 0]
|
||||||
|
elif category == 'languages':
|
||||||
|
# Use a human readable language string
|
||||||
|
formatter = calibre_langcode_to_name
|
||||||
|
items = [v for v in tcategories[category].values() if v.c > 0]
|
||||||
else:
|
else:
|
||||||
formatter = (lambda x:unicode(x))
|
formatter = (lambda x:unicode(x))
|
||||||
items = [v for v in tcategories[category].values() if v.c > 0]
|
items = [v for v in tcategories[category].values() if v.c > 0]
|
||||||
@ -2043,6 +2061,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if should_replace_field('comments'):
|
if should_replace_field('comments'):
|
||||||
doit(self.set_comment, id, mi.comments, notify=False, commit=False)
|
doit(self.set_comment, id, mi.comments, notify=False, commit=False)
|
||||||
|
|
||||||
|
if should_replace_field('languages'):
|
||||||
|
doit(self.set_languages, id, mi.languages, notify=False, commit=False)
|
||||||
|
|
||||||
# Setting series_index to zero is acceptable
|
# Setting series_index to zero is acceptable
|
||||||
if mi.series_index is not None:
|
if mi.series_index is not None:
|
||||||
doit(self.set_series_index, id, mi.series_index, notify=False,
|
doit(self.set_series_index, id, mi.series_index, notify=False,
|
||||||
@ -2265,6 +2286,37 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
|
def set_languages(self, book_id, languages, notify=True, commit=True):
|
||||||
|
self.conn.execute(
|
||||||
|
'DELETE FROM books_languages_link WHERE book=?', (book_id,))
|
||||||
|
self.conn.execute('''DELETE FROM languages WHERE (SELECT COUNT(id)
|
||||||
|
FROM books_languages_link WHERE
|
||||||
|
lang_code=languages.id) < 1''')
|
||||||
|
|
||||||
|
books_to_refresh = set([book_id])
|
||||||
|
final_languages = []
|
||||||
|
for l in languages:
|
||||||
|
lc = canonicalize_lang(l)
|
||||||
|
if not lc or lc in final_languages or lc in ('und', 'zxx', 'mis',
|
||||||
|
'mul'):
|
||||||
|
continue
|
||||||
|
final_languages.append(lc)
|
||||||
|
lc_id = self.conn.get('SELECT id FROM languages WHERE lang_code=?',
|
||||||
|
(lc,), all=False)
|
||||||
|
if lc_id is None:
|
||||||
|
lc_id = self.conn.execute('''INSERT INTO languages(lang_code)
|
||||||
|
VALUES (?)''', (lc,)).lastrowid
|
||||||
|
self.conn.execute('''INSERT INTO books_languages_link(book, lang_code)
|
||||||
|
VALUES (?,?)''', (book_id, lc_id))
|
||||||
|
self.dirtied(books_to_refresh, commit=False)
|
||||||
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
|
self.data.set(book_id, self.FIELD_MAP['languages'],
|
||||||
|
u','.join(final_languages), row_is_id=True)
|
||||||
|
if notify:
|
||||||
|
self.notify('metadata', [book_id])
|
||||||
|
return books_to_refresh
|
||||||
|
|
||||||
def set_timestamp(self, id, dt, notify=True, commit=True):
|
def set_timestamp(self, id, dt, notify=True, commit=True):
|
||||||
if dt:
|
if dt:
|
||||||
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
|
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
|
||||||
@ -2363,6 +2415,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return []
|
return []
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_languages_with_ids(self):
|
||||||
|
result = self.conn.get('SELECT id,lang_code FROM languages')
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result
|
||||||
|
|
||||||
def rename_tag(self, old_id, new_name):
|
def rename_tag(self, old_id, new_name):
|
||||||
# It is possible that new_name is in fact a set of names. Split it on
|
# It is possible that new_name is in fact a set of names. Split it on
|
||||||
# comma to find out. If it is, then rename the first one and append the
|
# comma to find out. If it is, then rename the first one and append the
|
||||||
|
@ -17,7 +17,7 @@ class TagsIcons(dict):
|
|||||||
|
|
||||||
category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
|
category_icons = ['authors', 'series', 'formats', 'publisher', 'rating',
|
||||||
'news', 'tags', 'custom:', 'user:', 'search',
|
'news', 'tags', 'custom:', 'user:', 'search',
|
||||||
'identifiers', 'gst']
|
'identifiers', 'languages', 'gst']
|
||||||
def __init__(self, icon_dict):
|
def __init__(self, icon_dict):
|
||||||
for a in self.category_icons:
|
for a in self.category_icons:
|
||||||
if a not in icon_dict:
|
if a not in icon_dict:
|
||||||
@ -37,6 +37,7 @@ category_icon_map = {
|
|||||||
'search' : 'search.png',
|
'search' : 'search.png',
|
||||||
'identifiers': 'identifiers.png',
|
'identifiers': 'identifiers.png',
|
||||||
'gst' : 'catalog.png',
|
'gst' : 'catalog.png',
|
||||||
|
'languages' : 'languages.png',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -114,6 +115,21 @@ class FieldMetadata(dict):
|
|||||||
'is_custom':False,
|
'is_custom':False,
|
||||||
'is_category':True,
|
'is_category':True,
|
||||||
'is_csp': False}),
|
'is_csp': False}),
|
||||||
|
('languages', {'table':'languages',
|
||||||
|
'column':'lang_code',
|
||||||
|
'link_column':'lang_code',
|
||||||
|
'category_sort':'lang_code',
|
||||||
|
'datatype':'text',
|
||||||
|
'is_multiple':{'cache_to_list': ',',
|
||||||
|
'ui_to_list': ',',
|
||||||
|
'list_to_ui': ', '},
|
||||||
|
'kind':'field',
|
||||||
|
'name':_('Languages'),
|
||||||
|
'search_terms':['languages', 'language'],
|
||||||
|
'is_custom':False,
|
||||||
|
'is_category':True,
|
||||||
|
'is_csp': False}),
|
||||||
|
|
||||||
('series', {'table':'series',
|
('series', {'table':'series',
|
||||||
'column':'name',
|
'column':'name',
|
||||||
'link_column':'series',
|
'link_column':'series',
|
||||||
|
@ -192,6 +192,74 @@ def get_language(lang):
|
|||||||
ans = iso639['by_3t'].get(lang, ans)
|
ans = iso639['by_3t'].get(lang, ans)
|
||||||
return translate(ans)
|
return translate(ans)
|
||||||
|
|
||||||
|
def calibre_langcode_to_name(lc, localize=True):
|
||||||
|
iso639 = _load_iso639()
|
||||||
|
translate = _ if localize else lambda x: x
|
||||||
|
try:
|
||||||
|
return translate(iso639['by_3t'][lc])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return lc
|
||||||
|
|
||||||
|
def canonicalize_lang(raw):
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
if not isinstance(raw, unicode):
|
||||||
|
raw = raw.decode('utf-8', 'ignore')
|
||||||
|
raw = raw.lower().strip()
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
raw = raw.replace('_', '-').partition('-')[0].strip()
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
iso639 = _load_iso639()
|
||||||
|
m2to3 = iso639['2to3']
|
||||||
|
|
||||||
|
if len(raw) == 2:
|
||||||
|
ans = m2to3.get(raw, None)
|
||||||
|
if ans is not None:
|
||||||
|
return ans
|
||||||
|
elif len(raw) == 3:
|
||||||
|
if raw in iso639['by_3t']:
|
||||||
|
return raw
|
||||||
|
if raw in iso639['3bto3t']:
|
||||||
|
return iso639['3bto3t'][raw]
|
||||||
|
|
||||||
|
return iso639['name_map'].get(raw, None)
|
||||||
|
|
||||||
|
_lang_map = None
|
||||||
|
|
||||||
|
def lang_map():
|
||||||
|
' Return mapping of ISO 639 3 letter codes to localized language names '
|
||||||
|
iso639 = _load_iso639()
|
||||||
|
translate = _
|
||||||
|
global _lang_map
|
||||||
|
if _lang_map is None:
|
||||||
|
_lang_map = {k:translate(v) for k, v in iso639['by_3t'].iteritems()}
|
||||||
|
return _lang_map
|
||||||
|
|
||||||
|
def langnames_to_langcodes(names):
|
||||||
|
'''
|
||||||
|
Given a list of localized language names return a mapping of the names to 3
|
||||||
|
letter ISO 639 language codes. If a name is not recognized, it is mapped to
|
||||||
|
None.
|
||||||
|
'''
|
||||||
|
iso639 = _load_iso639()
|
||||||
|
translate = _
|
||||||
|
ans = {}
|
||||||
|
names = set(names)
|
||||||
|
for k, v in iso639['by_3t'].iteritems():
|
||||||
|
tv = translate(v)
|
||||||
|
if tv in names:
|
||||||
|
names.remove(tv)
|
||||||
|
ans[tv] = k
|
||||||
|
if not names:
|
||||||
|
break
|
||||||
|
for x in names:
|
||||||
|
ans[x] = None
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
_udc = None
|
_udc = None
|
||||||
|
|
||||||
def get_udc():
|
def get_udc():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user