mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Pull from driver-dev
This commit is contained in:
commit
a9f3765632
@ -110,6 +110,18 @@ class CybookG3Input(InputProfile):
|
|||||||
fbase = 16
|
fbase = 16
|
||||||
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
||||||
|
|
||||||
|
class CybookOpusInput(InputProfile):
|
||||||
|
|
||||||
|
name = 'Cybook Opus'
|
||||||
|
short_name = 'cybook_opus'
|
||||||
|
description = _('This profile is intended for the Cybook Opus.')
|
||||||
|
|
||||||
|
# Screen size is a best guess
|
||||||
|
screen_size = (600, 800)
|
||||||
|
dpi = 200
|
||||||
|
fbase = 16
|
||||||
|
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
||||||
|
|
||||||
class KindleInput(InputProfile):
|
class KindleInput(InputProfile):
|
||||||
|
|
||||||
name = 'Kindle'
|
name = 'Kindle'
|
||||||
@ -222,6 +234,18 @@ class CybookG3Output(OutputProfile):
|
|||||||
fbase = 16
|
fbase = 16
|
||||||
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
||||||
|
|
||||||
|
class CybookOpusOutput(OutputProfile):
|
||||||
|
|
||||||
|
name = 'Cybook Opus'
|
||||||
|
short_name = 'cybook_opus'
|
||||||
|
description = _('This profile is intended for the Cybook Opus.')
|
||||||
|
|
||||||
|
# Screen size is a best guess
|
||||||
|
screen_size = (600, 800)
|
||||||
|
dpi = 200
|
||||||
|
fbase = 16
|
||||||
|
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
||||||
|
|
||||||
class KindleOutput(OutputProfile):
|
class KindleOutput(OutputProfile):
|
||||||
|
|
||||||
name = 'Kindle'
|
name = 'Kindle'
|
||||||
|
@ -276,6 +276,13 @@ def plugin_for_input_format(fmt):
|
|||||||
if fmt.lower() in plugin.file_types:
|
if fmt.lower() in plugin.file_types:
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
|
def all_input_formats():
|
||||||
|
formats = set([])
|
||||||
|
for plugin in input_format_plugins():
|
||||||
|
for format in plugin.file_types:
|
||||||
|
formats.add(format)
|
||||||
|
return formats
|
||||||
|
|
||||||
def available_input_formats():
|
def available_input_formats():
|
||||||
formats = set([])
|
formats = set([])
|
||||||
for plugin in input_format_plugins():
|
for plugin in input_format_plugins():
|
||||||
|
@ -7,7 +7,8 @@ import os, re, sys
|
|||||||
|
|
||||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||||
from calibre.customize.ui import input_profiles, output_profiles, \
|
from calibre.customize.ui import input_profiles, output_profiles, \
|
||||||
plugin_for_input_format, plugin_for_output_format
|
plugin_for_input_format, plugin_for_output_format, \
|
||||||
|
available_input_formats, available_output_formats
|
||||||
from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
|
from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
|
||||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
from calibre import extract, walk
|
from calibre import extract, walk
|
||||||
@ -19,10 +20,6 @@ def supported_input_formats():
|
|||||||
fmts.add(x)
|
fmts.add(x)
|
||||||
return fmts
|
return fmts
|
||||||
|
|
||||||
INPUT_FORMAT_PREFERENCES = ['cbr', 'cbz', 'cbc', 'lit', 'mobi', 'prc', 'azw', 'fb2', 'html',
|
|
||||||
'rtf', 'pdf', 'txt', 'pdb']
|
|
||||||
OUTPUT_FORMAT_PREFERENCES = ['epub', 'mobi', 'lit', 'pdf', 'pdb', 'txt']
|
|
||||||
|
|
||||||
class OptionValues(object):
|
class OptionValues(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -50,7 +47,7 @@ class Plumber(object):
|
|||||||
'tags', 'book_producer', 'language'
|
'tags', 'book_producer', 'language'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, input, output, log, report_progress=DummyReporter()):
|
def __init__(self, input, output, log, report_progress=DummyReporter(), dummy=False):
|
||||||
'''
|
'''
|
||||||
:param input: Path to input file.
|
:param input: Path to input file.
|
||||||
:param output: Path to output file/directory
|
:param output: Path to output file/directory
|
||||||
@ -318,6 +315,31 @@ OptionRecommendation(name='preprocess_html',
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
OptionRecommendation(name='remove_header',
|
||||||
|
recommended_value=False, level=OptionRecommendation.LOW,
|
||||||
|
help=_('Use a regular expression to try and remove the header.'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
OptionRecommendation(name='header_regex',
|
||||||
|
recommended_value='(?i)(?<=<hr>)((\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?\d+<br>\s*.*?\s*)|(\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?.*?<br>\s*\d+))(?=<br>)',
|
||||||
|
level=OptionRecommendation.LOW,
|
||||||
|
help=_('The regular expression to use to remove the header.'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
OptionRecommendation(name='remove_footer',
|
||||||
|
recommended_value=False, level=OptionRecommendation.LOW,
|
||||||
|
help=_('Use a regular expression to try and remove the footer.'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
OptionRecommendation(name='footer_regex',
|
||||||
|
recommended_value='(?i)(?<=<hr>)((\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?\d+<br>\s*.*?\s*)|(\s*<a name=\d+></a>((<img.+?>)*<br>\s*)?.*?<br>\s*\d+))(?=<br>)',
|
||||||
|
level=OptionRecommendation.LOW,
|
||||||
|
help=_('The regular expression to use to remove the footer.'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
OptionRecommendation(name='read_metadata_from_opf',
|
OptionRecommendation(name='read_metadata_from_opf',
|
||||||
recommended_value=None, level=OptionRecommendation.LOW,
|
recommended_value=None, level=OptionRecommendation.LOW,
|
||||||
@ -419,12 +441,28 @@ OptionRecommendation(name='list_recipes',
|
|||||||
self.input_fmt = input_fmt
|
self.input_fmt = input_fmt
|
||||||
self.output_fmt = output_fmt
|
self.output_fmt = output_fmt
|
||||||
|
|
||||||
|
|
||||||
|
self.all_format_options = set()
|
||||||
|
self.input_options = set()
|
||||||
|
self.output_options = set()
|
||||||
# Build set of all possible options. Two options are equal if their
|
# Build set of all possible options. Two options are equal if their
|
||||||
# names are the same.
|
# names are the same.
|
||||||
|
if not dummy:
|
||||||
self.input_options = self.input_plugin.options.union(
|
self.input_options = self.input_plugin.options.union(
|
||||||
self.input_plugin.common_options)
|
self.input_plugin.common_options)
|
||||||
self.output_options = self.output_plugin.options.union(
|
self.output_options = self.output_plugin.options.union(
|
||||||
self.output_plugin.common_options)
|
self.output_plugin.common_options)
|
||||||
|
else:
|
||||||
|
for fmt in available_input_formats():
|
||||||
|
input_plugin = plugin_for_input_format(fmt)
|
||||||
|
if input_plugin:
|
||||||
|
self.all_format_options = self.all_format_options.union(
|
||||||
|
input_plugin.options.union(input_plugin.common_options))
|
||||||
|
for fmt in available_output_formats():
|
||||||
|
output_plugin = plugin_for_output_format(fmt)
|
||||||
|
if output_plugin:
|
||||||
|
self.all_format_options = self.all_format_options.union(
|
||||||
|
output_plugin.options.union(output_plugin.common_options))
|
||||||
|
|
||||||
# Remove the options that have been disabled by recommendations from the
|
# Remove the options that have been disabled by recommendations from the
|
||||||
# plugins.
|
# plugins.
|
||||||
@ -469,7 +507,7 @@ OptionRecommendation(name='list_recipes',
|
|||||||
|
|
||||||
def get_option_by_name(self, name):
|
def get_option_by_name(self, name):
|
||||||
for group in (self.input_options, self.pipeline_options,
|
for group in (self.input_options, self.pipeline_options,
|
||||||
self.output_options):
|
self.output_options, self.all_format_options):
|
||||||
for rec in group:
|
for rec in group:
|
||||||
if rec.option == name:
|
if rec.option == name:
|
||||||
return rec
|
return rec
|
||||||
@ -535,7 +573,7 @@ OptionRecommendation(name='list_recipes',
|
|||||||
'''
|
'''
|
||||||
self.opts = OptionValues()
|
self.opts = OptionValues()
|
||||||
for group in (self.input_options, self.pipeline_options,
|
for group in (self.input_options, self.pipeline_options,
|
||||||
self.output_options):
|
self.output_options, self.all_format_options):
|
||||||
for rec in group:
|
for rec in group:
|
||||||
setattr(self.opts, rec.option.name, rec.recommended_value)
|
setattr(self.opts, rec.option.name, rec.recommended_value)
|
||||||
|
|
||||||
@ -696,7 +734,7 @@ def create_oebbook(log, path_or_stream, opts, input_plugin, reader=None,
|
|||||||
'''
|
'''
|
||||||
from calibre.ebooks.oeb.base import OEBBook
|
from calibre.ebooks.oeb.base import OEBBook
|
||||||
html_preprocessor = HTMLPreProcessor(input_plugin.preprocess_html,
|
html_preprocessor = HTMLPreProcessor(input_plugin.preprocess_html,
|
||||||
opts.preprocess_html, getattr(opts, 'pdf_line_length', 0.5))
|
opts.preprocess_html, opts)
|
||||||
oeb = OEBBook(log, html_preprocessor,
|
oeb = OEBBook(log, html_preprocessor,
|
||||||
pretty_print=opts.pretty_print, input_encoding=encoding)
|
pretty_print=opts.pretty_print, input_encoding=encoding)
|
||||||
if not populate:
|
if not populate:
|
||||||
|
@ -140,8 +140,6 @@ class HTMLPreProcessor(object):
|
|||||||
(re.compile(u'(?<=[\.,;\?!”"\'])[\s^ ]*(?=<)'), lambda match: ' '),
|
(re.compile(u'(?<=[\.,;\?!”"\'])[\s^ ]*(?=<)'), lambda match: ' '),
|
||||||
# Connect paragraphs split by -
|
# Connect paragraphs split by -
|
||||||
(re.compile(u'(?<=[^\s][-–])[\s]*(</p>)*[\s]*(<p>)*\s*(?=[^\s])'), lambda match: ''),
|
(re.compile(u'(?<=[^\s][-–])[\s]*(</p>)*[\s]*(<p>)*\s*(?=[^\s])'), lambda match: ''),
|
||||||
# Remove - that splits words
|
|
||||||
(re.compile(u'(?<=[^\s])[-–]+(?=[^\s])'), lambda match: ''),
|
|
||||||
# Add space before and after italics
|
# Add space before and after italics
|
||||||
(re.compile(u'(?<!“)<i>'), lambda match: ' <i>'),
|
(re.compile(u'(?<!“)<i>'), lambda match: ' <i>'),
|
||||||
(re.compile(r'</i>(?=\w)'), lambda match: '</i> '),
|
(re.compile(r'</i>(?=\w)'), lambda match: '</i> '),
|
||||||
@ -163,10 +161,10 @@ class HTMLPreProcessor(object):
|
|||||||
lambda match : '<h3 class="subtitle">%s</h3>'%(match.group(1),)),
|
lambda match : '<h3 class="subtitle">%s</h3>'%(match.group(1),)),
|
||||||
]
|
]
|
||||||
def __init__(self, input_plugin_preprocess, plugin_preprocess,
|
def __init__(self, input_plugin_preprocess, plugin_preprocess,
|
||||||
pdf_line_length):
|
extra_opts=None):
|
||||||
self.input_plugin_preprocess = input_plugin_preprocess
|
self.input_plugin_preprocess = input_plugin_preprocess
|
||||||
self.plugin_preprocess = plugin_preprocess
|
self.plugin_preprocess = plugin_preprocess
|
||||||
self.pdf_line_length = pdf_line_length
|
self.extra_opts = extra_opts
|
||||||
|
|
||||||
def is_baen(self, src):
|
def is_baen(self, src):
|
||||||
return re.compile(r'<meta\s+name="Publisher"\s+content=".*?Baen.*?"',
|
return re.compile(r'<meta\s+name="Publisher"\s+content=".*?Baen.*?"',
|
||||||
@ -187,18 +185,30 @@ class HTMLPreProcessor(object):
|
|||||||
elif self.is_book_designer(html):
|
elif self.is_book_designer(html):
|
||||||
rules = self.BOOK_DESIGNER
|
rules = self.BOOK_DESIGNER
|
||||||
elif self.is_pdftohtml(html):
|
elif self.is_pdftohtml(html):
|
||||||
length = line_length(html, self.pdf_line_length)
|
end_rules = []
|
||||||
line_length_rules = []
|
if getattr(self.extra_opts, 'unwrap_factor', None):
|
||||||
|
length = line_length(html, getattr(self.extra_opts, 'unwrap_factor'))
|
||||||
if length:
|
if length:
|
||||||
line_length_rules = [
|
end_rules.append(
|
||||||
# Un wrap using punctuation
|
# Un wrap using punctuation
|
||||||
(re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P<ital></(i|b|u)>)?\s*(<p.*?>)\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
|
(re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P<ital></(i|b|u)>)?\s*(<p.*?>)\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines),
|
||||||
]
|
)
|
||||||
|
|
||||||
rules = self.PDFTOHTML + line_length_rules
|
rules = self.PDFTOHTML + end_rules
|
||||||
else:
|
else:
|
||||||
rules = []
|
rules = []
|
||||||
for rule in self.PREPROCESS + rules:
|
|
||||||
|
pre_rules = []
|
||||||
|
if getattr(self.extra_opts, 'remove_header', None):
|
||||||
|
pre_rules.append(
|
||||||
|
(re.compile(getattr(self.extra_opts, 'header_regex')), lambda match : '')
|
||||||
|
)
|
||||||
|
if getattr(self.extra_opts, 'remove_footer', None):
|
||||||
|
pre_rules.append(
|
||||||
|
(re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '')
|
||||||
|
)
|
||||||
|
|
||||||
|
for rule in self.PREPROCESS + pre_rules + rules:
|
||||||
html = rule[0].sub(rule[1], html)
|
html = rule[0].sub(rule[1], html)
|
||||||
|
|
||||||
# Handle broken XHTML w/ SVG (ugh)
|
# Handle broken XHTML w/ SVG (ugh)
|
||||||
|
@ -35,7 +35,7 @@ class Clean(object):
|
|||||||
for x in list(self.oeb.guide):
|
for x in list(self.oeb.guide):
|
||||||
href = urldefrag(self.oeb.guide[x].href)[0]
|
href = urldefrag(self.oeb.guide[x].href)[0]
|
||||||
if x.lower() not in ('cover', 'titlepage', 'masthead', 'toc',
|
if x.lower() not in ('cover', 'titlepage', 'masthead', 'toc',
|
||||||
'title-page', 'copyright-page'):
|
'title-page', 'copyright-page', 'start'):
|
||||||
self.oeb.guide.remove(x)
|
self.oeb.guide.remove(x)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class PDFInput(InputFormatPlugin):
|
|||||||
options = set([
|
options = set([
|
||||||
OptionRecommendation(name='no_images', recommended_value=False,
|
OptionRecommendation(name='no_images', recommended_value=False,
|
||||||
help=_('Do not extract images from the document')),
|
help=_('Do not extract images from the document')),
|
||||||
OptionRecommendation(name='pdf_line_length', recommended_value=0.5,
|
OptionRecommendation(name='unwrap_factor', recommended_value=0.5,
|
||||||
help=_('Scale used to determine the length at which a line should '
|
help=_('Scale used to determine the length at which a line should '
|
||||||
'be unwrapped. Valid values are a decimal between 0 and 1. The '
|
'be unwrapped. Valid values are a decimal between 0 and 1. The '
|
||||||
'default is 0.5, this is the median line length.')),
|
'default is 0.5, this is the median line length.')),
|
||||||
@ -42,12 +42,7 @@ class PDFInput(InputFormatPlugin):
|
|||||||
images = os.listdir(os.getcwd())
|
images = os.listdir(os.getcwd())
|
||||||
images.remove('index.html')
|
images.remove('index.html')
|
||||||
for i in images:
|
for i in images:
|
||||||
# Remove the - from the file name because it causes problems.
|
manifest.append((i, None))
|
||||||
# The reference to the image with the - will be changed to not
|
|
||||||
# include it later in the conversion process.
|
|
||||||
new_i = i.replace('-', '')
|
|
||||||
os.rename(i, new_i)
|
|
||||||
manifest.append((new_i, None))
|
|
||||||
log.debug('Generating manifest...')
|
log.debug('Generating manifest...')
|
||||||
opf.create_manifest(manifest)
|
opf.create_manifest(manifest)
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ def _config():
|
|||||||
help='Show donation button')
|
help='Show donation button')
|
||||||
c.add_opt('asked_library_thing_password', default=False,
|
c.add_opt('asked_library_thing_password', default=False,
|
||||||
help='Asked library thing password at least once.')
|
help='Asked library thing password at least once.')
|
||||||
|
c.add_opt('search_as_you_type', default=True,
|
||||||
|
help='Start searching as you type. If this is disabled then search will '
|
||||||
|
'only take place when the Enter or Return key is pressed.')
|
||||||
return ConfigProxy(c)
|
return ConfigProxy(c)
|
||||||
|
|
||||||
config = _config()
|
config = _config()
|
||||||
|
@ -15,7 +15,8 @@ from calibre.gui2.convert.page_setup import PageSetupWidget
|
|||||||
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
|
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
|
||||||
from calibre.gui2.convert.toc import TOCWidget
|
from calibre.gui2.convert.toc import TOCWidget
|
||||||
from calibre.gui2.convert import GuiRecommendations
|
from calibre.gui2.convert import GuiRecommendations
|
||||||
from calibre.ebooks.conversion.plumber import Plumber, OUTPUT_FORMAT_PREFERENCES
|
from calibre.ebooks.conversion.plumber import Plumber
|
||||||
|
from calibre.utils.config import prefs
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
|
|
||||||
class BulkConfig(Config):
|
class BulkConfig(Config):
|
||||||
@ -102,7 +103,7 @@ class BulkConfig(Config):
|
|||||||
preferred_output_format = preferred_output_format if \
|
preferred_output_format = preferred_output_format if \
|
||||||
preferred_output_format and preferred_output_format \
|
preferred_output_format and preferred_output_format \
|
||||||
in output_formats else sort_formats_by_preference(output_formats,
|
in output_formats else sort_formats_by_preference(output_formats,
|
||||||
OUTPUT_FORMAT_PREFERENCES)[0]
|
prefs['output_format'])[0]
|
||||||
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
||||||
output_formats])))
|
output_formats])))
|
||||||
self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
|
self.output_formats.setCurrentIndex(output_formats.index(preferred_output_format))
|
||||||
@ -117,4 +118,3 @@ class BulkConfig(Config):
|
|||||||
self._recommendations = recs
|
self._recommendations = recs
|
||||||
ResizableDialog.accept(self)
|
ResizableDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,21 +35,17 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
||||||
|
|
||||||
def initialize_metadata_options(self):
|
def initialize_metadata_options(self):
|
||||||
all_series = self.db.all_series()
|
self.initialize_combos()
|
||||||
all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
|
||||||
for series in all_series:
|
|
||||||
self.series.addItem(series[1])
|
|
||||||
self.series.setCurrentIndex(-1)
|
|
||||||
|
|
||||||
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
mi = self.db.get_metadata(self.book_id, index_is_id=True)
|
||||||
self.title.setText(mi.title)
|
self.title.setText(mi.title)
|
||||||
if mi.authors:
|
if mi.authors:
|
||||||
self.author.setText(authors_to_string(mi.authors))
|
self.author.setCurrentIndex(self.author.findText(authors_to_string(mi.authors)))
|
||||||
else:
|
if mi.publisher:
|
||||||
self.author.setText('')
|
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
|
||||||
self.publisher.setText(mi.publisher if mi.publisher else '')
|
|
||||||
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
||||||
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
||||||
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
self.comment.setText(mi.comments if mi.comments else '')
|
self.comment.setText(mi.comments if mi.comments else '')
|
||||||
if mi.series:
|
if mi.series:
|
||||||
self.series.setCurrentIndex(self.series.findText(mi.series))
|
self.series.setCurrentIndex(self.series.findText(mi.series))
|
||||||
@ -66,6 +62,39 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
if not pm.isNull():
|
if not pm.isNull():
|
||||||
self.cover.setPixmap(pm)
|
self.cover.setPixmap(pm)
|
||||||
|
|
||||||
|
def initialize_combos(self):
|
||||||
|
self.initalize_authors()
|
||||||
|
self.initialize_series()
|
||||||
|
self.initialize_publisher()
|
||||||
|
|
||||||
|
def initalize_authors(self):
|
||||||
|
all_authors = self.db.all_authors()
|
||||||
|
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_authors:
|
||||||
|
id, name = i
|
||||||
|
name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
|
||||||
|
self.author.addItem(name)
|
||||||
|
self.author.setCurrentIndex(-1)
|
||||||
|
|
||||||
|
def initialize_series(self):
|
||||||
|
all_series = self.db.all_series()
|
||||||
|
all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_series:
|
||||||
|
id, name = i
|
||||||
|
self.series.addItem(name)
|
||||||
|
self.series.setCurrentIndex(-1)
|
||||||
|
|
||||||
|
def initialize_publisher(self):
|
||||||
|
all_publishers = self.db.all_publishers()
|
||||||
|
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_publishers:
|
||||||
|
id, name = i
|
||||||
|
self.publisher.addItem(name)
|
||||||
|
self.publisher.setCurrentIndex(-1)
|
||||||
|
|
||||||
def get_title_and_authors(self):
|
def get_title_and_authors(self):
|
||||||
title = unicode(self.title.text()).strip()
|
title = unicode(self.title.text()).strip()
|
||||||
if not title:
|
if not title:
|
||||||
|
@ -143,19 +143,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="EnLineEdit" name="author">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Change the author(s) of this book. Multiple authors should be separated by an &. If the author name contains an &, use && to represent it.</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_6">
|
<widget class="QLabel" name="label_6">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -195,13 +182,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="EnLineEdit" name="publisher">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Change the publisher of this book</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label_4">
|
<widget class="QLabel" name="label_4">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -216,7 +196,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="EnLineEdit" name="tags">
|
<widget class="TagsLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -276,6 +256,20 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="EnComboBox" name="publisher">
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="EnComboBox" name="author">
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -329,11 +323,16 @@
|
|||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>TagsLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
<include location="../images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
<include location="../../../../../gui2/images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -14,6 +14,6 @@ class PluginWidget(Widget, Ui_Form):
|
|||||||
|
|
||||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||||
Widget.__init__(self, parent, 'pdf_input',
|
Widget.__init__(self, parent, 'pdf_input',
|
||||||
['no_images', 'pdf_line_length'])
|
['no_images', 'unwrap_factor'])
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="1" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Line Un-Wrapping Factor:</string>
|
<string>Line Un-Wrapping Factor:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="2" column="0">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -34,8 +34,8 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QDoubleSpinBox" name="opt_pdf_line_length">
|
<widget class="QDoubleSpinBox" name="opt_unwrap_factor">
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<double>1.000000000000000</double>
|
<double>1.000000000000000</double>
|
||||||
</property>
|
</property>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QCheckBox" name="opt_no_images">
|
<widget class="QCheckBox" name="opt_no_images">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>No Images</string>
|
<string>No Images</string>
|
||||||
|
@ -20,11 +20,10 @@ from calibre.gui2.convert.page_setup import PageSetupWidget
|
|||||||
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
|
from calibre.gui2.convert.structure_detection import StructureDetectionWidget
|
||||||
from calibre.gui2.convert.toc import TOCWidget
|
from calibre.gui2.convert.toc import TOCWidget
|
||||||
|
|
||||||
|
from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats
|
||||||
from calibre.ebooks.conversion.plumber import Plumber, supported_input_formats, \
|
|
||||||
INPUT_FORMAT_PREFERENCES, OUTPUT_FORMAT_PREFERENCES
|
|
||||||
from calibre.customize.ui import available_output_formats
|
from calibre.customize.ui import available_output_formats
|
||||||
from calibre.customize.conversion import OptionRecommendation
|
from calibre.customize.conversion import OptionRecommendation
|
||||||
|
from calibre.utils.config import prefs
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
|
|
||||||
class NoSupportedInputFormats(Exception):
|
class NoSupportedInputFormats(Exception):
|
||||||
@ -33,11 +32,11 @@ class NoSupportedInputFormats(Exception):
|
|||||||
def sort_formats_by_preference(formats, prefs):
|
def sort_formats_by_preference(formats, prefs):
|
||||||
def fcmp(x, y):
|
def fcmp(x, y):
|
||||||
try:
|
try:
|
||||||
x = prefs.index(x)
|
x = prefs.index(x.upper())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
x = sys.maxint
|
x = sys.maxint
|
||||||
try:
|
try:
|
||||||
y = prefs.index(y)
|
y = prefs.index(y.upper())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
y = sys.maxint
|
y = sys.maxint
|
||||||
return cmp(x, y)
|
return cmp(x, y)
|
||||||
@ -206,11 +205,11 @@ class Config(ResizableDialog, Ui_Dialog):
|
|||||||
preferred_input_format = preferred_input_format if \
|
preferred_input_format = preferred_input_format if \
|
||||||
preferred_input_format in input_formats else \
|
preferred_input_format in input_formats else \
|
||||||
sort_formats_by_preference(input_formats,
|
sort_formats_by_preference(input_formats,
|
||||||
INPUT_FORMAT_PREFERENCES)[0]
|
prefs['input_format_order'])[0]
|
||||||
preferred_output_format = preferred_output_format if \
|
preferred_output_format = preferred_output_format if \
|
||||||
preferred_output_format in output_formats else \
|
preferred_output_format in output_formats else \
|
||||||
sort_formats_by_preference(output_formats,
|
sort_formats_by_preference(output_formats,
|
||||||
OUTPUT_FORMAT_PREFERENCES)[0]
|
prefs['output_format'])[0]
|
||||||
self.input_formats.addItems(list(map(QString, [x.upper() for x in
|
self.input_formats.addItems(list(map(QString, [x.upper() for x in
|
||||||
input_formats])))
|
input_formats])))
|
||||||
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
self.output_formats.addItems(list(map(QString, [x.upper() for x in
|
||||||
|
@ -6,6 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from calibre.gui2.convert.structure_detection_ui import Ui_Form
|
from calibre.gui2.convert.structure_detection_ui import Ui_Form
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
@ -23,7 +24,8 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
|||||||
['chapter', 'chapter_mark',
|
['chapter', 'chapter_mark',
|
||||||
'remove_first_image',
|
'remove_first_image',
|
||||||
'insert_metadata', 'page_breaks_before',
|
'insert_metadata', 'page_breaks_before',
|
||||||
'preprocess_html']
|
'preprocess_html', 'remove_header', 'header_regex',
|
||||||
|
'remove_footer', 'footer_regex']
|
||||||
)
|
)
|
||||||
self.db, self.book_id = db, book_id
|
self.db, self.book_id = db, book_id
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
@ -31,8 +33,16 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
|||||||
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
|
self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
|
||||||
'(XPath expression):'))
|
'(XPath expression):'))
|
||||||
|
|
||||||
|
|
||||||
def pre_commit_check(self):
|
def pre_commit_check(self):
|
||||||
|
for x in ('header_regex', 'footer_regex'):
|
||||||
|
x = getattr(self, 'opt_'+x)
|
||||||
|
try:
|
||||||
|
pat = unicode(x.text())
|
||||||
|
re.compile(pat)
|
||||||
|
except Exception, err:
|
||||||
|
error_dialog(self, _('Invalid regular expression'),
|
||||||
|
_('Invalid regular expression: %s')%err).exec_()
|
||||||
|
return False
|
||||||
for x in ('chapter', 'page_breaks_before'):
|
for x in ('chapter', 'page_breaks_before'):
|
||||||
x = getattr(self, 'opt_'+x)
|
x = getattr(self, 'opt_'+x)
|
||||||
if not x.check():
|
if not x.check():
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="XPathEdit" name="opt_chapter" native="true"/>
|
||||||
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -62,20 +65,27 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Footer regular expression:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_footer_regex</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="opt_preprocess_html">
|
<widget class="QCheckBox" name="opt_preprocess_html">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Preprocess input file to possibly improve structure detection</string>
|
<string>&Preprocess input file to possibly improve structure detection</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="11" column="0" colspan="2">
|
||||||
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
|
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0" colspan="2">
|
<item row="12" column="0">
|
||||||
<widget class="XPathEdit" name="opt_chapter" native="true"/>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -88,6 +98,36 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Header regular expression:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>opt_header_regex</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_remove_footer">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove F&ooter</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_remove_header">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove H&eader</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0" colspan="2">
|
||||||
|
<widget class="QLineEdit" name="opt_footer_regex"/>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0" colspan="2">
|
||||||
|
<widget class="QLineEdit" name="opt_header_regex"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
@ -22,7 +22,8 @@ from calibre.library import server_config
|
|||||||
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
|
from calibre.customize.ui import initialized_plugins, is_disabled, enable_plugin, \
|
||||||
disable_plugin, customize_plugin, \
|
disable_plugin, customize_plugin, \
|
||||||
plugin_customization, add_plugin, \
|
plugin_customization, add_plugin, \
|
||||||
remove_plugin, input_format_plugins, \
|
remove_plugin, all_input_formats, \
|
||||||
|
input_format_plugins, \
|
||||||
output_format_plugins, available_output_formats
|
output_format_plugins, available_output_formats
|
||||||
from calibre.utils.smtp import config as smtp_prefs
|
from calibre.utils.smtp import config as smtp_prefs
|
||||||
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
|
from calibre.gui2.convert.look_and_feel import LookAndFeelWidget
|
||||||
@ -39,7 +40,7 @@ class ConfigTabs(QTabWidget):
|
|||||||
log = Log()
|
log = Log()
|
||||||
log.outputs = []
|
log.outputs = []
|
||||||
|
|
||||||
self.plumber = Plumber('dummt.epub', 'dummy.epub', log)
|
self.plumber = Plumber('dummy.epub', 'dummy.epub', log, dummy=True)
|
||||||
|
|
||||||
def widget_factory(cls):
|
def widget_factory(cls):
|
||||||
return cls(self, self.plumber.get_option_by_name,
|
return cls(self, self.plumber.get_option_by_name,
|
||||||
@ -337,6 +338,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
||||||
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
||||||
|
|
||||||
|
input_map = prefs['input_format_order']
|
||||||
|
all_formats = set()
|
||||||
|
for fmt in all_input_formats():
|
||||||
|
all_formats.add(fmt.upper())
|
||||||
|
for format in input_map + list(all_formats.difference(input_map)):
|
||||||
|
item = QListWidgetItem(format, self.input_order)
|
||||||
|
item.setData(Qt.UserRole, QVariant(format))
|
||||||
|
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
|
||||||
|
|
||||||
|
self.connect(self.input_up, SIGNAL('clicked()'), self.up_input)
|
||||||
|
self.connect(self.input_down, SIGNAL('clicked()'), self.down_input)
|
||||||
|
|
||||||
dirs = config['frequently_used_directories']
|
dirs = config['frequently_used_directories']
|
||||||
rn = config['use_roman_numerals_for_series_number']
|
rn = config['use_roman_numerals_for_series_number']
|
||||||
self.timeout.setValue(prefs['network_timeout'])
|
self.timeout.setValue(prefs['network_timeout'])
|
||||||
@ -424,6 +437,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.password.setText(opts.password if opts.password else '')
|
self.password.setText(opts.password if opts.password else '')
|
||||||
self.auto_launch.setChecked(config['autolaunch_server'])
|
self.auto_launch.setChecked(config['autolaunch_server'])
|
||||||
self.systray_icon.setChecked(config['systray_icon'])
|
self.systray_icon.setChecked(config['systray_icon'])
|
||||||
|
self.search_as_you_type.setChecked(config['search_as_you_type'])
|
||||||
self.sync_news.setChecked(config['upload_news_to_device'])
|
self.sync_news.setChecked(config['upload_news_to_device'])
|
||||||
self.delete_news.setChecked(config['delete_news_from_library_on_upload'])
|
self.delete_news.setChecked(config['delete_news_from_library_on_upload'])
|
||||||
p = {'normal':0, 'high':1, 'low':2}[prefs['worker_process_priority']]
|
p = {'normal':0, 'high':1, 'low':2}[prefs['worker_process_priority']]
|
||||||
@ -553,6 +567,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
plugin.name + _(' cannot be removed. It is a '
|
plugin.name + _(' cannot be removed. It is a '
|
||||||
'builtin plugin. Try disabling it instead.')).exec_()
|
'builtin plugin. Try disabling it instead.')).exec_()
|
||||||
|
|
||||||
|
def up_input(self):
|
||||||
|
idx = self.input_order.currentRow()
|
||||||
|
if idx > 0:
|
||||||
|
self.input_order.insertItem(idx-1, self.input_order.takeItem(idx))
|
||||||
|
self.input_order.setCurrentRow(idx-1)
|
||||||
|
|
||||||
|
def down_input(self):
|
||||||
|
idx = self.input_order.currentRow()
|
||||||
|
if idx < self.input_order.count()-1:
|
||||||
|
self.input_order.insertItem(idx+1, self.input_order.takeItem(idx))
|
||||||
|
self.input_order.setCurrentRow(idx+1)
|
||||||
|
|
||||||
def up_column(self):
|
def up_column(self):
|
||||||
idx = self.columns.currentRow()
|
idx = self.columns.currentRow()
|
||||||
@ -656,6 +681,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||||
prefs['network_timeout'] = int(self.timeout.value())
|
prefs['network_timeout'] = int(self.timeout.value())
|
||||||
path = qstring_to_unicode(self.location.text())
|
path = qstring_to_unicode(self.location.text())
|
||||||
|
input_cols = [unicode(self.input_order.item(i).data(Qt.UserRole).toString()) for i in range(self.input_order.count())]
|
||||||
|
prefs['input_format_order'] = input_cols
|
||||||
cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked]
|
cols = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked]
|
||||||
if not cols:
|
if not cols:
|
||||||
cols = ['title']
|
cols = ['title']
|
||||||
@ -681,6 +708,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
sc.set('max_cover', mcs)
|
sc.set('max_cover', mcs)
|
||||||
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
|
||||||
config['upload_news_to_device'] = self.sync_news.isChecked()
|
config['upload_news_to_device'] = self.sync_news.isChecked()
|
||||||
|
config['search_as_you_type'] = self.search_as_you_type.isChecked()
|
||||||
fmts = []
|
fmts = []
|
||||||
for i in range(self.viewer.count()):
|
for i in range(self.viewer.count()):
|
||||||
if self.viewer.item(i).checkState() == Qt.Checked:
|
if self.viewer.item(i).checkState() == Qt.Checked:
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>800</width>
|
||||||
<height>557</height>
|
<height>583</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -232,6 +232,68 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_5">
|
||||||
|
<property name="title">
|
||||||
|
<string>Preferred &input format order:</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="input_order">
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="input_up">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../images.qrc">
|
||||||
|
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="input_down">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../images.qrc">
|
||||||
|
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="dirs_box">
|
<widget class="QGroupBox" name="dirs_box">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -364,6 +426,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="search_as_you_type">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search as you type</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="sync_news">
|
<widget class="QCheckBox" name="sync_news">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -529,15 +601,6 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
<zorder>roman_numerals</zorder>
|
|
||||||
<zorder>groupBox_2</zorder>
|
|
||||||
<zorder>systray_icon</zorder>
|
|
||||||
<zorder>sync_news</zorder>
|
|
||||||
<zorder>delete_news</zorder>
|
|
||||||
<zorder>separate_cover_flow</zorder>
|
|
||||||
<zorder>systray_notifications</zorder>
|
|
||||||
<zorder></zorder>
|
|
||||||
<zorder></zorder>
|
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="page_6">
|
<widget class="QWidget" name="page_6">
|
||||||
<layout class="QGridLayout" name="gridLayout_6">
|
<layout class="QGridLayout" name="gridLayout_6">
|
||||||
|
@ -9,7 +9,8 @@ from PyQt4.QtGui import QDialog
|
|||||||
from calibre.gui2 import qstring_to_unicode
|
from calibre.gui2 import qstring_to_unicode
|
||||||
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string
|
from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string, \
|
||||||
|
authors_to_string
|
||||||
|
|
||||||
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||||
|
|
||||||
@ -25,29 +26,63 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync)
|
QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync)
|
||||||
QObject.connect(self.rating, SIGNAL('valueChanged(int)'), self.rating_changed)
|
QObject.connect(self.rating, SIGNAL('valueChanged(int)'), self.rating_changed)
|
||||||
|
|
||||||
all_series = self.db.all_series()
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
|
self.remove_tags.update_tags_cache(self.db.all_tags())
|
||||||
|
|
||||||
for i in all_series:
|
self.initialize_combos()
|
||||||
id, name = i
|
|
||||||
self.series.addItem(name)
|
|
||||||
|
|
||||||
for f in self.db.all_formats():
|
for f in self.db.all_formats():
|
||||||
self.remove_format.addItem(f)
|
self.remove_format.addItem(f)
|
||||||
|
|
||||||
self.remove_format.setCurrentIndex(-1)
|
self.remove_format.setCurrentIndex(-1)
|
||||||
|
|
||||||
self.series.lineEdit().setText('')
|
|
||||||
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.series_changed)
|
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.series_changed)
|
||||||
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.series_changed)
|
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.series_changed)
|
||||||
QObject.connect(self.tag_editor_button, SIGNAL('clicked()'), self.tag_editor)
|
QObject.connect(self.tag_editor_button, SIGNAL('clicked()'), self.tag_editor)
|
||||||
|
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
|
def initialize_combos(self):
|
||||||
|
self.initalize_authors()
|
||||||
|
self.initialize_series()
|
||||||
|
self.initialize_publisher()
|
||||||
|
|
||||||
|
def initalize_authors(self):
|
||||||
|
all_authors = self.db.all_authors()
|
||||||
|
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_authors:
|
||||||
|
id, name = i
|
||||||
|
name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
|
||||||
|
self.authors.addItem(name)
|
||||||
|
self.authors.setEditText('')
|
||||||
|
|
||||||
|
def initialize_series(self):
|
||||||
|
all_series = self.db.all_series()
|
||||||
|
all_series.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_series:
|
||||||
|
id, name = i
|
||||||
|
self.series.addItem(name)
|
||||||
|
self.series.setEditText('')
|
||||||
|
|
||||||
|
def initialize_publisher(self):
|
||||||
|
all_publishers = self.db.all_publishers()
|
||||||
|
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
|
||||||
|
for i in all_publishers:
|
||||||
|
id, name = i
|
||||||
|
self.publisher.addItem(name)
|
||||||
|
self.publisher.setEditText('')
|
||||||
|
|
||||||
def tag_editor(self):
|
def tag_editor(self):
|
||||||
d = TagEditor(self, self.db, None)
|
d = TagEditor(self, self.db, None)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
self.tags.setText(tag_string)
|
self.tags.setText(tag_string)
|
||||||
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
|
self.remove_tags.update_tags_cache(self.db.all_tags())
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
for id in self.ids:
|
for id in self.ids:
|
||||||
|
@ -45,16 +45,6 @@
|
|||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
|
||||||
<cstring>authors</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1" colspan="2">
|
|
||||||
<widget class="EnLineEdit" name="authors">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Change the author(s) of this book. Multiple authors should be separated by an &. If the author name contains an &, use && to represent it.</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
@ -65,9 +55,6 @@
|
|||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
|
||||||
<cstring>authors</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1" colspan="2">
|
<item row="2" column="1" colspan="2">
|
||||||
@ -117,16 +104,6 @@
|
|||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
|
||||||
<cstring>publisher</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1" colspan="2">
|
|
||||||
<widget class="EnLineEdit" name="publisher">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Change the publisher of this book</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="5" column="0">
|
||||||
@ -143,7 +120,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1">
|
<item row="5" column="1">
|
||||||
<widget class="EnLineEdit" name="tags">
|
<widget class="TagsLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -174,7 +151,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1" colspan="2">
|
<item row="6" column="1" colspan="2">
|
||||||
<widget class="EnLineEdit" name="remove_tags">
|
<widget class="TagsLineEdit" name="remove_tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Comma separated list of tags to remove from the books. </string>
|
<string>Comma separated list of tags to remove from the books. </string>
|
||||||
</property>
|
</property>
|
||||||
@ -235,6 +212,20 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="EnComboBox" name="authors">
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="EnComboBox" name="publisher">
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -265,6 +256,11 @@
|
|||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>TagsLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
|
@ -13,7 +13,7 @@ import traceback
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate
|
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate
|
||||||
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog, QCompleter
|
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog
|
||||||
|
|
||||||
from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
|
from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
|
||||||
choose_files, pixmap_to_data, choose_images, ResizableDialog
|
choose_files, pixmap_to_data, choose_images, ResizableDialog
|
||||||
@ -80,13 +80,6 @@ class Format(QListWidgetItem):
|
|||||||
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
|
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
|
||||||
text, parent, QListWidgetItem.UserType)
|
text, parent, QListWidgetItem.UserType)
|
||||||
|
|
||||||
class AuthorCompleter(QCompleter):
|
|
||||||
|
|
||||||
def __init__(self, db):
|
|
||||||
all_authors = db.all_authors()
|
|
||||||
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
|
||||||
QCompleter.__init__(self, [x[1] for x in all_authors])
|
|
||||||
|
|
||||||
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||||
|
|
||||||
COVER_FETCH_TIMEOUT = 240 # seconds
|
COVER_FETCH_TIMEOUT = 240 # seconds
|
||||||
@ -233,8 +226,6 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.cover_changed = False
|
self.cover_changed = False
|
||||||
self.cpixmap = None
|
self.cpixmap = None
|
||||||
self.cover.setAcceptDrops(True)
|
self.cover.setAcceptDrops(True)
|
||||||
self._author_completer = AuthorCompleter(self.db)
|
|
||||||
self.authors.setCompleter(self._author_completer)
|
|
||||||
self.pubdate.setMinimumDate(QDate(100,1,1))
|
self.pubdate.setMinimumDate(QDate(100,1,1))
|
||||||
self.connect(self.cover, SIGNAL('cover_changed()'), self.cover_dropped)
|
self.connect(self.cover, SIGNAL('cover_changed()'), self.cover_dropped)
|
||||||
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
|
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
|
||||||
@ -265,16 +256,11 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if not isbn:
|
if not isbn:
|
||||||
isbn = ''
|
isbn = ''
|
||||||
self.isbn.setText(isbn)
|
self.isbn.setText(isbn)
|
||||||
au = self.db.authors(row)
|
|
||||||
if au:
|
|
||||||
au = [a.strip().replace('|', ',') for a in au.split(',')]
|
|
||||||
self.authors.setText(authors_to_string(au))
|
|
||||||
else:
|
|
||||||
self.authors.setText('')
|
|
||||||
aus = self.db.author_sort(row)
|
aus = self.db.author_sort(row)
|
||||||
self.author_sort.setText(aus if aus else '')
|
self.author_sort.setText(aus if aus else '')
|
||||||
tags = self.db.tags(row)
|
tags = self.db.tags(row)
|
||||||
self.tags.setText(tags if tags else '')
|
self.tags.setText(', '.join(tags.split(',')) if tags else '')
|
||||||
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
rating = self.db.rating(row)
|
rating = self.db.rating(row)
|
||||||
if rating > 0:
|
if rating > 0:
|
||||||
self.rating.setValue(int(rating/2.))
|
self.rating.setValue(int(rating/2.))
|
||||||
@ -295,7 +281,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
Format(self.formats, ext, size)
|
Format(self.formats, ext, size)
|
||||||
|
|
||||||
|
|
||||||
self.initialize_series_and_publisher()
|
self.initialize_combos()
|
||||||
|
|
||||||
self.series_index.setValue(self.db.series_index(row))
|
self.series_index.setValue(self.db.series_index(row))
|
||||||
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
|
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
|
||||||
@ -331,6 +317,30 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
def cover_dropped(self):
|
def cover_dropped(self):
|
||||||
self.cover_changed = True
|
self.cover_changed = True
|
||||||
|
|
||||||
|
def initialize_combos(self):
|
||||||
|
self.initalize_authors()
|
||||||
|
self.initialize_series()
|
||||||
|
self.initialize_publisher()
|
||||||
|
|
||||||
|
self.layout().activate()
|
||||||
|
|
||||||
|
def initalize_authors(self):
|
||||||
|
all_authors = self.db.all_authors()
|
||||||
|
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
|
author_id = self.db.author_id(self.row)
|
||||||
|
idx, c = None, 0
|
||||||
|
for i in all_authors:
|
||||||
|
id, name = i
|
||||||
|
if id == author_id:
|
||||||
|
idx = c
|
||||||
|
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||||
|
self.authors.addItem(authors_to_string(name))
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
self.authors.setEditText('')
|
||||||
|
if idx is not None:
|
||||||
|
self.authors.setCurrentIndex(idx)
|
||||||
|
|
||||||
def initialize_series(self):
|
def initialize_series(self):
|
||||||
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
|
||||||
all_series = self.db.all_series()
|
all_series = self.db.all_series()
|
||||||
@ -349,8 +359,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
self.series.setCurrentIndex(idx)
|
self.series.setCurrentIndex(idx)
|
||||||
self.enable_series_index()
|
self.enable_series_index()
|
||||||
|
|
||||||
def initialize_series_and_publisher(self):
|
def initialize_publisher(self):
|
||||||
self.initialize_series()
|
|
||||||
all_publishers = self.db.all_publishers()
|
all_publishers = self.db.all_publishers()
|
||||||
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
|
||||||
publisher_id = self.db.publisher_id(self.row)
|
publisher_id = self.db.publisher_id(self.row)
|
||||||
@ -366,15 +375,13 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
if idx is not None:
|
if idx is not None:
|
||||||
self.publisher.setCurrentIndex(idx)
|
self.publisher.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
|
||||||
self.layout().activate()
|
|
||||||
|
|
||||||
def edit_tags(self):
|
def edit_tags(self):
|
||||||
d = TagEditor(self, self.db, self.row)
|
d = TagEditor(self, self.db, self.row)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
tag_string = ', '.join(d.tags)
|
tag_string = ', '.join(d.tags)
|
||||||
self.tags.setText(tag_string)
|
self.tags.setText(tag_string)
|
||||||
|
self.tags.update_tags_cache(self.db.all_tags())
|
||||||
|
|
||||||
def fetch_cover(self):
|
def fetch_cover(self):
|
||||||
isbn = unicode(self.isbn.text()).strip()
|
isbn = unicode(self.isbn.text()).strip()
|
||||||
|
@ -121,9 +121,6 @@
|
|||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
|
||||||
<cstring>authors</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
@ -225,7 +222,7 @@
|
|||||||
<item row="5" column="1" colspan="2">
|
<item row="5" column="1" colspan="2">
|
||||||
<layout class="QHBoxLayout" name="_2">
|
<layout class="QHBoxLayout" name="_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="EnLineEdit" name="tags">
|
<widget class="TagsLineEdit" name="tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
<string>Tags categorize the book. This is particularly useful while searching. <br><br>They can be any words or phrases, separated by commas.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -345,9 +342,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="EnLineEdit" name="authors"/>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="QDoubleSpinBox" name="series_index">
|
<widget class="QDoubleSpinBox" name="series_index">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -371,6 +365,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="EnComboBox" name="authors">
|
||||||
|
<property name="editable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -651,11 +652,15 @@
|
|||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>TagsLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>title</tabstop>
|
<tabstop>title</tabstop>
|
||||||
<tabstop>swap_button</tabstop>
|
<tabstop>swap_button</tabstop>
|
||||||
<tabstop>authors</tabstop>
|
|
||||||
<tabstop>author_sort</tabstop>
|
<tabstop>author_sort</tabstop>
|
||||||
<tabstop>auto_author_sort</tabstop>
|
<tabstop>auto_author_sort</tabstop>
|
||||||
<tabstop>rating</tabstop>
|
<tabstop>rating</tabstop>
|
||||||
|
@ -9,7 +9,8 @@ from math import cos, sin, pi
|
|||||||
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
||||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||||
QPen, QStyle, QPainter, QLineEdit, \
|
QPen, QStyle, QPainter, QLineEdit, \
|
||||||
QPalette, QImage, QApplication, QMenu, QStyledItemDelegate
|
QPalette, QImage, QApplication, QMenu, \
|
||||||
|
QStyledItemDelegate, QCompleter
|
||||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||||
SIGNAL, QObject, QSize, QModelIndex, QDate
|
SIGNAL, QObject, QSize, QModelIndex, QDate
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ from calibre.utils.pyparsing import ParseException
|
|||||||
from calibre.library.database2 import FIELD_MAP
|
from calibre.library.database2 import FIELD_MAP
|
||||||
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
|
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
|
||||||
error_dialog
|
error_dialog
|
||||||
|
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
from calibre.ebooks.metadata import string_to_authors, fmt_sidx
|
from calibre.ebooks.metadata import string_to_authors, fmt_sidx
|
||||||
@ -111,6 +113,45 @@ class PubDateDelegate(QStyledItemDelegate):
|
|||||||
qde.setCalendarPopup(True)
|
qde.setCalendarPopup(True)
|
||||||
return qde
|
return qde
|
||||||
|
|
||||||
|
class TextDelegate(QStyledItemDelegate):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
'''
|
||||||
|
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
|
||||||
|
auto-complete will be used.
|
||||||
|
'''
|
||||||
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
|
self.auto_complete_function = None
|
||||||
|
|
||||||
|
def set_auto_complete_function(self, f):
|
||||||
|
self.auto_complete_function = f
|
||||||
|
|
||||||
|
def createEditor(self, parent, option, index):
|
||||||
|
editor = EnLineEdit(parent)
|
||||||
|
if self.auto_complete_function:
|
||||||
|
complete_items = [i[1] for i in self.auto_complete_function()]
|
||||||
|
completer = QCompleter(complete_items, self)
|
||||||
|
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
completer.setCompletionMode(QCompleter.InlineCompletion)
|
||||||
|
editor.setCompleter(completer)
|
||||||
|
return editor
|
||||||
|
|
||||||
|
class TagsDelegate(QStyledItemDelegate):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
|
self.db = None
|
||||||
|
|
||||||
|
def set_database(self, db):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
def createEditor(self, parent, option, index):
|
||||||
|
if self.db:
|
||||||
|
editor = TagsLineEdit(parent, self.db.all_tags())
|
||||||
|
else:
|
||||||
|
editor = EnLineEdit(parent)
|
||||||
|
return editor
|
||||||
|
|
||||||
class BooksModel(QAbstractTableModel):
|
class BooksModel(QAbstractTableModel):
|
||||||
headers = {
|
headers = {
|
||||||
@ -148,21 +189,7 @@ class BooksModel(QAbstractTableModel):
|
|||||||
if cols != self.column_map:
|
if cols != self.column_map:
|
||||||
self.column_map = cols
|
self.column_map = cols
|
||||||
self.reset()
|
self.reset()
|
||||||
try:
|
self.emit(SIGNAL('columns_sorted()'))
|
||||||
idx = self.column_map.index('rating')
|
|
||||||
except ValueError:
|
|
||||||
idx = -1
|
|
||||||
try:
|
|
||||||
tidx = self.column_map.index('timestamp')
|
|
||||||
except ValueError:
|
|
||||||
tidx = -1
|
|
||||||
try:
|
|
||||||
pidx = self.column_map.index('pubdate')
|
|
||||||
except ValueError:
|
|
||||||
pidx = -1
|
|
||||||
|
|
||||||
self.emit(SIGNAL('columns_sorted(int,int,int)'), idx, tidx, pidx)
|
|
||||||
|
|
||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
@ -649,34 +676,45 @@ class BooksView(TableView):
|
|||||||
self.rating_delegate = LibraryDelegate(self)
|
self.rating_delegate = LibraryDelegate(self)
|
||||||
self.timestamp_delegate = DateDelegate(self)
|
self.timestamp_delegate = DateDelegate(self)
|
||||||
self.pubdate_delegate = PubDateDelegate(self)
|
self.pubdate_delegate = PubDateDelegate(self)
|
||||||
|
self.tags_delegate = TagsDelegate(self)
|
||||||
|
self.authors_delegate = TextDelegate(self)
|
||||||
|
self.series_delegate = TextDelegate(self)
|
||||||
|
self.publisher_delegate = TextDelegate(self)
|
||||||
self.display_parent = parent
|
self.display_parent = parent
|
||||||
self._model = modelcls(self)
|
self._model = modelcls(self)
|
||||||
self.setModel(self._model)
|
self.setModel(self._model)
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
try:
|
for i in range(10):
|
||||||
cm = self._model.column_map
|
self.setItemDelegateForColumn(i, TextDelegate(self))
|
||||||
self.columns_sorted(cm.index('rating') if 'rating' in cm else -1,
|
self.columns_sorted()
|
||||||
cm.index('timestamp') if 'timestamp' in cm else -1,
|
|
||||||
cm.index('pubdate') if 'pubdate' in cm else -1)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
|
||||||
self._model.current_changed)
|
self._model.current_changed)
|
||||||
self.connect(self._model, SIGNAL('columns_sorted(int,int,int)'),
|
self.connect(self._model, SIGNAL('columns_sorted()'),
|
||||||
self.columns_sorted, Qt.QueuedConnection)
|
self.columns_sorted, Qt.QueuedConnection)
|
||||||
|
|
||||||
def columns_sorted(self, rating_col, timestamp_col, pubdate_col):
|
def columns_sorted(self):
|
||||||
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.setItemDelegateForColumn(i, self.itemDelegate())
|
self.setItemDelegateForColumn(i, self.itemDelegate())
|
||||||
if rating_col > -1:
|
|
||||||
self.setItemDelegateForColumn(rating_col, self.rating_delegate)
|
cm = self._model.column_map
|
||||||
if timestamp_col > -1:
|
|
||||||
self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate)
|
if 'rating' in cm:
|
||||||
if pubdate_col > -1:
|
self.setItemDelegateForColumn(cm.index('rating'), self.rating_delegate)
|
||||||
self.setItemDelegateForColumn(pubdate_col, self.pubdate_delegate)
|
if 'timestamp' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('timestamp'), self.timestamp_delegate)
|
||||||
|
if 'pubdate' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('pubdate'), self.pubdate_delegate)
|
||||||
|
if 'tags' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('tags'), self.tags_delegate)
|
||||||
|
if 'authors' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('authors'), self.authors_delegate)
|
||||||
|
if 'publisher' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('publisher'), self.publisher_delegate)
|
||||||
|
if 'series' in cm:
|
||||||
|
self.setItemDelegateForColumn(cm.index('series'), self.series_delegate)
|
||||||
|
|
||||||
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
|
def set_context_menu(self, edit_metadata, send_to_device, convert, view,
|
||||||
save, open_folder, book_details, similar_menu=None):
|
save, open_folder, book_details, similar_menu=None):
|
||||||
@ -739,6 +777,10 @@ class BooksView(TableView):
|
|||||||
|
|
||||||
def set_database(self, db):
|
def set_database(self, db):
|
||||||
self._model.set_database(db)
|
self._model.set_database(db)
|
||||||
|
self.tags_delegate.set_database(db)
|
||||||
|
self.authors_delegate.set_auto_complete_function(db.all_authors)
|
||||||
|
self.series_delegate.set_auto_complete_function(db.all_series)
|
||||||
|
self.publisher_delegate.set_auto_complete_function(db.all_publishers)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._model.close()
|
self._model.close()
|
||||||
@ -769,10 +811,13 @@ class DeviceBooksView(BooksView):
|
|||||||
self.resize_on_select = False
|
self.resize_on_select = False
|
||||||
self.rating_delegate = None
|
self.rating_delegate = None
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
self.setItemDelegateForColumn(i, self.itemDelegate())
|
self.setItemDelegateForColumn(i, TextDelegate(self))
|
||||||
self.setDragDropMode(self.NoDragDrop)
|
self.setDragDropMode(self.NoDragDrop)
|
||||||
self.setAcceptDrops(False)
|
self.setAcceptDrops(False)
|
||||||
|
|
||||||
|
def set_database(self, db):
|
||||||
|
self._model.set_database(db)
|
||||||
|
|
||||||
def resizeColumnsToContents(self):
|
def resizeColumnsToContents(self):
|
||||||
QTableView.resizeColumnsToContents(self)
|
QTableView.resizeColumnsToContents(self)
|
||||||
self.columns_resized = True
|
self.columns_resized = True
|
||||||
@ -1062,6 +1107,7 @@ class SearchBox(QLineEdit):
|
|||||||
QLineEdit.__init__(self, parent)
|
QLineEdit.__init__(self, parent)
|
||||||
self.help_text = help_text
|
self.help_text = help_text
|
||||||
self.initial_state = True
|
self.initial_state = True
|
||||||
|
self.as_you_type = True
|
||||||
self.default_palette = QApplication.palette(self)
|
self.default_palette = QApplication.palette(self)
|
||||||
self.gray = QPalette(self.default_palette)
|
self.gray = QPalette(self.default_palette)
|
||||||
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
|
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
|
||||||
@ -1094,6 +1140,9 @@ class SearchBox(QLineEdit):
|
|||||||
if self.initial_state:
|
if self.initial_state:
|
||||||
self.normalize_state()
|
self.normalize_state()
|
||||||
self.initial_state = False
|
self.initial_state = False
|
||||||
|
if not self.as_you_type:
|
||||||
|
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
||||||
|
self.do_search()
|
||||||
QLineEdit.keyPressEvent(self, event)
|
QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
@ -1103,6 +1152,7 @@ class SearchBox(QLineEdit):
|
|||||||
QLineEdit.mouseReleaseEvent(self, event)
|
QLineEdit.mouseReleaseEvent(self, event)
|
||||||
|
|
||||||
def text_edited_slot(self, text):
|
def text_edited_slot(self, text):
|
||||||
|
if self.as_you_type:
|
||||||
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
|
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
|
||||||
self.prev_text = text
|
self.prev_text = text
|
||||||
self.timer = self.startTimer(self.__class__.INTERVAL)
|
self.timer = self.startTimer(self.__class__.INTERVAL)
|
||||||
@ -1110,6 +1160,9 @@ class SearchBox(QLineEdit):
|
|||||||
def timerEvent(self, event):
|
def timerEvent(self, event):
|
||||||
self.killTimer(event.timerId())
|
self.killTimer(event.timerId())
|
||||||
if event.timerId() == self.timer:
|
if event.timerId() == self.timer:
|
||||||
|
self.do_search()
|
||||||
|
|
||||||
|
def do_search(self):
|
||||||
text = qstring_to_unicode(self.text())
|
text = qstring_to_unicode(self.text())
|
||||||
refinement = text.startswith(self.prev_search) and ':' not in text
|
refinement = text.startswith(self.prev_search) and ':' not in text
|
||||||
self.prev_search = text
|
self.prev_search = text
|
||||||
@ -1132,3 +1185,6 @@ class SearchBox(QLineEdit):
|
|||||||
self.end(False)
|
self.end(False)
|
||||||
self.initial_state = False
|
self.initial_state = False
|
||||||
|
|
||||||
|
def search_as_you_type(self, enabled):
|
||||||
|
self.as_you_type = enabled
|
||||||
|
|
||||||
|
@ -147,6 +147,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.system_tray_icon.hide()
|
self.system_tray_icon.hide()
|
||||||
else:
|
else:
|
||||||
self.system_tray_icon.show()
|
self.system_tray_icon.show()
|
||||||
|
self.search.search_as_you_type(config['search_as_you_type'])
|
||||||
self.system_tray_menu = QMenu(self)
|
self.system_tray_menu = QMenu(self)
|
||||||
self.restore_action = self.system_tray_menu.addAction(
|
self.restore_action = self.system_tray_menu.addAction(
|
||||||
QIcon(':/images/page.svg'), _('&Restore'))
|
QIcon(':/images/page.svg'), _('&Restore'))
|
||||||
@ -311,12 +312,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
cm.addAction(_('Convert individually'))
|
cm.addAction(_('Convert individually'))
|
||||||
cm.addAction(_('Bulk convert'))
|
cm.addAction(_('Bulk convert'))
|
||||||
self.action_convert.setMenu(cm)
|
self.action_convert.setMenu(cm)
|
||||||
|
self._convert_single_hook = partial(self.convert_ebook, bulk=False)
|
||||||
QObject.connect(cm.actions()[0],
|
QObject.connect(cm.actions()[0],
|
||||||
SIGNAL('triggered(bool)'), self.convert_single)
|
SIGNAL('triggered(bool)'), self._convert_single_hook)
|
||||||
|
self._convert_bulk_hook = partial(self.convert_ebook, bulk=True)
|
||||||
QObject.connect(cm.actions()[1],
|
QObject.connect(cm.actions()[1],
|
||||||
SIGNAL('triggered(bool)'), self.convert_bulk)
|
SIGNAL('triggered(bool)'), self._convert_bulk_hook)
|
||||||
QObject.connect(self.action_convert,
|
QObject.connect(self.action_convert,
|
||||||
SIGNAL('triggered(bool)'), self.convert_single)
|
SIGNAL('triggered(bool)'), self.convert_ebook)
|
||||||
self.convert_menu = cm
|
self.convert_menu = cm
|
||||||
|
|
||||||
pm = QMenu()
|
pm = QMenu()
|
||||||
@ -1161,31 +1164,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
return None
|
return None
|
||||||
return [self.library_view.model().db.id(r) for r in rows]
|
return [self.library_view.model().db.id(r) for r in rows]
|
||||||
|
|
||||||
def convert_bulk(self, checked):
|
def convert_ebook(self, checked, bulk=None):
|
||||||
book_ids = self.get_books_for_conversion()
|
book_ids = self.get_books_for_conversion()
|
||||||
if book_ids is None: return
|
if book_ids is None: return
|
||||||
previous = self.library_view.currentIndex()
|
previous = self.library_view.currentIndex()
|
||||||
rows = [x.row() for x in \
|
rows = [x.row() for x in \
|
||||||
self.library_view.selectionModel().selectedRows()]
|
self.library_view.selectionModel().selectedRows()]
|
||||||
|
if bulk or (bulk is None and len(book_ids) > 1):
|
||||||
jobs, changed, bad = convert_bulk_ebook(self,
|
jobs, changed, bad = convert_bulk_ebook(self,
|
||||||
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
|
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
|
||||||
for func, args, desc, fmt, id, temp_files in jobs:
|
else:
|
||||||
if id not in bad:
|
|
||||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
|
||||||
func, args=args, description=desc)
|
|
||||||
self.conversion_jobs[job] = (temp_files, fmt, id)
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
self.library_view.model().refresh_rows(rows)
|
|
||||||
current = self.library_view.currentIndex()
|
|
||||||
self.library_view.model().current_changed(current, previous)
|
|
||||||
|
|
||||||
def convert_single(self, checked):
|
|
||||||
book_ids = self.get_books_for_conversion()
|
|
||||||
if book_ids is None: return
|
|
||||||
previous = self.library_view.currentIndex()
|
|
||||||
rows = [x.row() for x in \
|
|
||||||
self.library_view.selectionModel().selectedRows()]
|
|
||||||
jobs, changed, bad = convert_single_ebook(self,
|
jobs, changed, bad = convert_single_ebook(self,
|
||||||
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
|
self.library_view.model().db, book_ids, out_format=prefs['output_format'])
|
||||||
for func, args, desc, fmt, id, temp_files in jobs:
|
for func, args, desc, fmt, id, temp_files in jobs:
|
||||||
@ -1369,51 +1357,51 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
|
|
||||||
def view_book(self, triggered):
|
def view_book(self, triggered):
|
||||||
rows = self.current_view().selectionModel().selectedRows()
|
rows = self.current_view().selectionModel().selectedRows()
|
||||||
if self.current_view() is self.library_view:
|
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
self._launch_viewer()
|
self._launch_viewer()
|
||||||
return
|
return
|
||||||
|
|
||||||
row = rows[0].row()
|
if len(rows) >= 3:
|
||||||
|
if not question_dialog(self, _('Multiple Books Selected'),
|
||||||
|
_('You are attempting to open %d books. Opening too many '
|
||||||
|
'books at once can be slow and have a negative effect on the '
|
||||||
|
'responsiveness of your computer. Once started the process '
|
||||||
|
'cannot be stopped until complete. Do you wish to continue?'
|
||||||
|
% len(rows))):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.current_view() is self.library_view:
|
||||||
|
for row in rows:
|
||||||
|
row = row.row()
|
||||||
|
|
||||||
formats = self.library_view.model().db.formats(row).upper()
|
formats = self.library_view.model().db.formats(row).upper()
|
||||||
formats = formats.split(',')
|
formats = formats.split(',')
|
||||||
title = self.library_view.model().db.title(row)
|
title = self.library_view.model().db.title(row)
|
||||||
id = self.library_view.model().db.id(row)
|
|
||||||
format = None
|
|
||||||
if len(formats) == 1:
|
|
||||||
format = formats[0]
|
|
||||||
if 'LRF' in formats:
|
|
||||||
format = 'LRF'
|
|
||||||
if 'EPUB' in formats:
|
|
||||||
format = 'EPUB'
|
|
||||||
if 'MOBI' in formats:
|
|
||||||
format = 'MOBI'
|
|
||||||
if not formats:
|
|
||||||
d = error_dialog(self, _('Cannot view'),
|
|
||||||
_('%s has no available formats.')%(title,))
|
|
||||||
d.exec_()
|
|
||||||
return
|
|
||||||
if format is None:
|
|
||||||
d = ChooseFormatDialog(self, _('Choose the format to view'),
|
|
||||||
formats)
|
|
||||||
d.exec_()
|
|
||||||
if d.result() == QDialog.Accepted:
|
|
||||||
format = d.format()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
if not formats:
|
||||||
|
error_dialog(self, _('Cannot view'),
|
||||||
|
_('%s has no available formats.')%(title,), show=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
in_prefs = False
|
||||||
|
for format in prefs['input_format_order']:
|
||||||
|
if format in formats:
|
||||||
|
in_prefs = True
|
||||||
self.view_format(row, format)
|
self.view_format(row, format)
|
||||||
|
break
|
||||||
|
if not in_prefs:
|
||||||
|
self.view_format(row, format[0])
|
||||||
else:
|
else:
|
||||||
paths = self.current_view().model().paths(rows)
|
paths = self.current_view().model().paths(rows)
|
||||||
if paths:
|
for path in paths:
|
||||||
pt = PersistentTemporaryFile('_viewer_'+\
|
pt = PersistentTemporaryFile('_viewer_'+\
|
||||||
os.path.splitext(paths[0])[1])
|
os.path.splitext(path)[1])
|
||||||
self.persistent_files.append(pt)
|
self.persistent_files.append(pt)
|
||||||
pt.close()
|
pt.close()
|
||||||
self.device_manager.view_book(\
|
self.device_manager.view_book(\
|
||||||
Dispatcher(self.book_downloaded_for_viewing),
|
Dispatcher(self.book_downloaded_for_viewing),
|
||||||
paths[0], pt.name)
|
path, pt.name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
@ -1441,6 +1429,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.content_server = d.server
|
self.content_server = d.server
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
|
self.search.search_as_you_type(config['search_as_you_type'])
|
||||||
self.tool_bar.setToolButtonStyle(
|
self.tool_bar.setToolButtonStyle(
|
||||||
Qt.ToolButtonTextUnderIcon if \
|
Qt.ToolButtonTextUnderIcon if \
|
||||||
config['show_text_in_toolbar'] else \
|
config['show_text_in_toolbar'] else \
|
||||||
|
@ -10,7 +10,8 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
|
|||||||
QPixmap, QMovie, QPalette, QTimer, QDialog, \
|
QPixmap, QMovie, QPalette, QTimer, QDialog, \
|
||||||
QAbstractListModel, QVariant, Qt, SIGNAL, \
|
QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||||
QRegExp, QSettings, QSize, QModelIndex, \
|
QRegExp, QSettings, QSize, QModelIndex, \
|
||||||
QAbstractButton, QPainter, QLineEdit, QComboBox
|
QAbstractButton, QPainter, QLineEdit, QComboBox, \
|
||||||
|
QMenu, QStringListModel, QCompleter
|
||||||
|
|
||||||
from calibre.gui2 import human_readable, NONE, TableView, \
|
from calibre.gui2 import human_readable, NONE, TableView, \
|
||||||
qstring_to_unicode, error_dialog
|
qstring_to_unicode, error_dialog
|
||||||
@ -460,12 +461,30 @@ class LineEditECM(object):
|
|||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = self.createStandardContextMenu()
|
menu = self.createStandardContextMenu()
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
action_title_case = menu.addAction('Title Case')
|
|
||||||
|
|
||||||
|
case_menu = QMenu(_('Change Case'))
|
||||||
|
action_upper_case = case_menu.addAction(_('Upper Case'))
|
||||||
|
action_lower_case = case_menu.addAction(_('Lower Case'))
|
||||||
|
action_swap_case = case_menu.addAction(_('Swap Case'))
|
||||||
|
action_title_case = case_menu.addAction(_('Title Case'))
|
||||||
|
|
||||||
|
self.connect(action_upper_case, SIGNAL('triggered()'), self.upper_case)
|
||||||
|
self.connect(action_lower_case, SIGNAL('triggered()'), self.lower_case)
|
||||||
|
self.connect(action_swap_case, SIGNAL('triggered()'), self.swap_case)
|
||||||
self.connect(action_title_case, SIGNAL('triggered()'), self.title_case)
|
self.connect(action_title_case, SIGNAL('triggered()'), self.title_case)
|
||||||
|
|
||||||
|
menu.addMenu(case_menu)
|
||||||
menu.exec_(event.globalPos())
|
menu.exec_(event.globalPos())
|
||||||
|
|
||||||
|
def upper_case(self):
|
||||||
|
self.setText(qstring_to_unicode(self.text()).upper())
|
||||||
|
|
||||||
|
def lower_case(self):
|
||||||
|
self.setText(qstring_to_unicode(self.text()).lower())
|
||||||
|
|
||||||
|
def swap_case(self):
|
||||||
|
self.setText(qstring_to_unicode(self.text()).swapcase())
|
||||||
|
|
||||||
def title_case(self):
|
def title_case(self):
|
||||||
self.setText(qstring_to_unicode(self.text()).title())
|
self.setText(qstring_to_unicode(self.text()).title())
|
||||||
|
|
||||||
@ -481,6 +500,84 @@ class EnLineEdit(LineEditECM, QLineEdit):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TagsCompleter(QCompleter):
|
||||||
|
|
||||||
|
'''
|
||||||
|
A completer object that completes a list of tags. It is used in conjunction
|
||||||
|
with a CompleterLineEdit.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, parent, all_tags):
|
||||||
|
QCompleter.__init__(self, all_tags, parent)
|
||||||
|
self.all_tags = set(all_tags)
|
||||||
|
|
||||||
|
def update(self, text_tags, completion_prefix):
|
||||||
|
tags = list(self.all_tags.difference(text_tags))
|
||||||
|
model = QStringListModel(tags, self)
|
||||||
|
self.setModel(model)
|
||||||
|
|
||||||
|
self.setCompletionPrefix(completion_prefix)
|
||||||
|
if completion_prefix.strip() != '':
|
||||||
|
self.complete()
|
||||||
|
|
||||||
|
def update_tags_cache(self, tags):
|
||||||
|
self.all_tags = set(tags)
|
||||||
|
model = QStringListModel(tags, self)
|
||||||
|
self.setModel(model)
|
||||||
|
|
||||||
|
|
||||||
|
class TagsLineEdit(EnLineEdit):
|
||||||
|
|
||||||
|
'''
|
||||||
|
A QLineEdit that can complete parts of text separated by separator.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, parent=0, tags=[]):
|
||||||
|
EnLineEdit.__init__(self, parent)
|
||||||
|
|
||||||
|
self.separator = ','
|
||||||
|
|
||||||
|
self.connect(self, SIGNAL('textChanged(QString)'), self.text_changed)
|
||||||
|
|
||||||
|
self.completer = TagsCompleter(self, tags)
|
||||||
|
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
|
||||||
|
self.connect(self,
|
||||||
|
SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
|
||||||
|
self.completer.update)
|
||||||
|
self.connect(self.completer, SIGNAL('activated(QString)'),
|
||||||
|
self.complete_text)
|
||||||
|
|
||||||
|
self.completer.setWidget(self)
|
||||||
|
|
||||||
|
def update_tags_cache(self, tags):
|
||||||
|
self.completer.update_tags_cache(tags)
|
||||||
|
|
||||||
|
def text_changed(self, text):
|
||||||
|
all_text = qstring_to_unicode(text)
|
||||||
|
text = all_text[:self.cursorPosition()]
|
||||||
|
prefix = text.split(',')[-1].strip()
|
||||||
|
|
||||||
|
text_tags = []
|
||||||
|
for t in all_text.split(self.separator):
|
||||||
|
t1 = qstring_to_unicode(t).strip()
|
||||||
|
if t1 != '':
|
||||||
|
text_tags.append(t)
|
||||||
|
text_tags = list(set(text_tags))
|
||||||
|
|
||||||
|
self.emit(SIGNAL('text_changed(PyQt_PyObject, PyQt_PyObject)'),
|
||||||
|
text_tags, prefix)
|
||||||
|
|
||||||
|
def complete_text(self, text):
|
||||||
|
cursor_pos = self.cursorPosition()
|
||||||
|
before_text = qstring_to_unicode(self.text())[:cursor_pos]
|
||||||
|
after_text = qstring_to_unicode(self.text())[cursor_pos:]
|
||||||
|
prefix_len = len(before_text.split(',')[-1].strip())
|
||||||
|
self.setText('%s%s%s %s' % (before_text[:cursor_pos - prefix_len],
|
||||||
|
text, self.separator, after_text))
|
||||||
|
self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
|
||||||
|
|
||||||
|
|
||||||
class EnComboBox(QComboBox):
|
class EnComboBox(QComboBox):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@ -493,6 +590,8 @@ class EnComboBox(QComboBox):
|
|||||||
QComboBox.__init__(self, *args)
|
QComboBox.__init__(self, *args)
|
||||||
self.setLineEdit(EnLineEdit(self))
|
self.setLineEdit(EnLineEdit(self))
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return qstring_to_unicode(self.currentText())
|
||||||
|
|
||||||
class PythonHighlighter(QSyntaxHighlighter):
|
class PythonHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
|
@ -92,6 +92,12 @@ class CybookG3(Device):
|
|||||||
manufacturer = 'Booken'
|
manufacturer = 'Booken'
|
||||||
id = 'cybookg3'
|
id = 'cybookg3'
|
||||||
|
|
||||||
|
class CybookOpus(CybookG3):
|
||||||
|
|
||||||
|
name = 'Cybook Opus'
|
||||||
|
output_format = 'EPUB'
|
||||||
|
id = 'cybook_opus'
|
||||||
|
|
||||||
class BeBook(Device):
|
class BeBook(Device):
|
||||||
|
|
||||||
name = 'BeBook or BeBook Mini'
|
name = 'BeBook or BeBook Mini'
|
||||||
|
@ -928,6 +928,10 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def author_id(self, index, index_is_id=False):
|
||||||
|
id = index if index_is_id else self.id(index)
|
||||||
|
return self.conn.get('SELECT author from books_authors_link WHERE book=?', (id,), all=False)
|
||||||
|
|
||||||
def isbn(self, idx, index_is_id=False):
|
def isbn(self, idx, index_is_id=False):
|
||||||
id = idx if index_is_id else self.id(idx)
|
id = idx if index_is_id else self.id(idx)
|
||||||
return self.conn.get('SELECT isbn FROM books WHERE id=?',(id,), all=False)
|
return self.conn.get('SELECT isbn FROM books WHERE id=?',(id,), all=False)
|
||||||
|
@ -51,7 +51,7 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
|||||||
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
|
||||||
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
||||||
'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15,
|
'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15,
|
||||||
'lccn':16, 'pubdate':17, 'flags':18}
|
'lccn':16, 'pubdate':17, 'flags':18, 'cover':19}
|
||||||
INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys()))
|
INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys()))
|
||||||
|
|
||||||
|
|
||||||
@ -198,19 +198,40 @@ class ResultCache(SearchQueryParser):
|
|||||||
query = query.decode('utf-8')
|
query = query.decode('utf-8')
|
||||||
if location in ('tag', 'author', 'format'):
|
if location in ('tag', 'author', 'format'):
|
||||||
location += 's'
|
location += 's'
|
||||||
all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn')
|
all = ('title', 'authors', 'publisher', 'tags', 'comments', 'series', 'formats', 'isbn', 'rating', 'cover')
|
||||||
MAP = {}
|
MAP = {}
|
||||||
for x in all:
|
for x in all:
|
||||||
MAP[x] = FIELD_MAP[x]
|
MAP[x] = FIELD_MAP[x]
|
||||||
|
EXCLUDE_FIELDS = [MAP['rating'], MAP['cover']]
|
||||||
location = [location] if location != 'all' else list(MAP.keys())
|
location = [location] if location != 'all' else list(MAP.keys())
|
||||||
for i, loc in enumerate(location):
|
for i, loc in enumerate(location):
|
||||||
location[i] = MAP[loc]
|
location[i] = MAP[loc]
|
||||||
|
try:
|
||||||
|
rating_query = int(query) * 2
|
||||||
|
except:
|
||||||
|
rating_query = None
|
||||||
for item in self._data:
|
for item in self._data:
|
||||||
if item is None: continue
|
if item is None: continue
|
||||||
for loc in location:
|
for loc in location:
|
||||||
if item[loc] and query in item[loc].lower():
|
if query == 'false' and not item[loc]:
|
||||||
|
if isinstance(item[loc], basestring):
|
||||||
|
if item[loc].strip() != '':
|
||||||
|
continue
|
||||||
matches.add(item[0])
|
matches.add(item[0])
|
||||||
break
|
break
|
||||||
|
if query == 'true' and item[loc]:
|
||||||
|
if isinstance(item[loc], basestring):
|
||||||
|
if item[loc].strip() == '':
|
||||||
|
continue
|
||||||
|
matches.add(item[0])
|
||||||
|
break
|
||||||
|
if rating_query and item[loc] and loc == MAP['rating'] and rating_query == int(item[loc]):
|
||||||
|
matches.add(item[0])
|
||||||
|
break
|
||||||
|
if item[loc] and loc not in EXCLUDE_FIELDS and query in item[loc].lower():
|
||||||
|
matches.add(item[0])
|
||||||
|
break
|
||||||
|
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def remove(self, id):
|
def remove(self, id):
|
||||||
@ -242,15 +263,16 @@ class ResultCache(SearchQueryParser):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def refresh_ids(self, conn, ids):
|
def refresh_ids(self, db, ids):
|
||||||
'''
|
'''
|
||||||
Refresh the data in the cache for books identified by ids.
|
Refresh the data in the cache for books identified by ids.
|
||||||
Returns a list of affected rows or None if the rows are filtered.
|
Returns a list of affected rows or None if the rows are filtered.
|
||||||
'''
|
'''
|
||||||
for id in ids:
|
for id in ids:
|
||||||
try:
|
try:
|
||||||
self._data[id] = conn.get('SELECT * from meta WHERE id=?',
|
self._data[id] = db.conn.get('SELECT * from meta WHERE id=?',
|
||||||
(id,))[0]
|
(id,))[0]
|
||||||
|
self._data[id].append(db.has_cover(id, index_is_id=True))
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@ -259,12 +281,13 @@ class ResultCache(SearchQueryParser):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def books_added(self, ids, conn):
|
def books_added(self, ids, db):
|
||||||
if not ids:
|
if not ids:
|
||||||
return
|
return
|
||||||
self._data.extend(repeat(None, max(ids)-len(self._data)+2))
|
self._data.extend(repeat(None, max(ids)-len(self._data)+2))
|
||||||
for id in ids:
|
for id in ids:
|
||||||
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
|
self._data[id] = db.conn.get('SELECT * from meta WHERE id=?', (id,))[0]
|
||||||
|
self._data[id].append(db.has_cover(id, index_is_id=True))
|
||||||
self._map[0:0] = ids
|
self._map[0:0] = ids
|
||||||
self._map_filtered[0:0] = ids
|
self._map_filtered[0:0] = ids
|
||||||
|
|
||||||
@ -282,6 +305,9 @@ class ResultCache(SearchQueryParser):
|
|||||||
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
|
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
|
||||||
for r in temp:
|
for r in temp:
|
||||||
self._data[r[0]] = r
|
self._data[r[0]] = r
|
||||||
|
for item in self._data:
|
||||||
|
if item is not None:
|
||||||
|
item.append(db.has_cover(item[0], index_is_id=True))
|
||||||
self._map = [i[0] for i in self._data if i is not None]
|
self._map = [i[0] for i in self._data if i is not None]
|
||||||
if field is not None:
|
if field is not None:
|
||||||
self.sort(field, ascending)
|
self.sort(field, ascending)
|
||||||
@ -400,7 +426,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.refresh = functools.partial(self.data.refresh, self)
|
self.refresh = functools.partial(self.data.refresh, self)
|
||||||
self.sort = self.data.sort
|
self.sort = self.data.sort
|
||||||
self.index = self.data.index
|
self.index = self.data.index
|
||||||
self.refresh_ids = functools.partial(self.data.refresh_ids, self.conn)
|
self.refresh_ids = functools.partial(self.data.refresh_ids, self)
|
||||||
self.row = self.data.row
|
self.row = self.data.row
|
||||||
self.has_id = self.data.has_id
|
self.has_id = self.data.has_id
|
||||||
self.count = self.data.count
|
self.count = self.data.count
|
||||||
@ -1014,7 +1040,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.set_rating(id, val, notify=False)
|
self.set_rating(id, val, notify=False)
|
||||||
elif column == 'tags':
|
elif column == 'tags':
|
||||||
self.set_tags(id, val.split(','), append=False, notify=False)
|
self.set_tags(id, val.split(','), append=False, notify=False)
|
||||||
self.data.refresh_ids(self.conn, [id])
|
self.data.refresh_ids(self, [id])
|
||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
@ -1195,7 +1221,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
if id:
|
if id:
|
||||||
self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id))
|
self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id))
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.refresh_ids(self.conn, [book_id])
|
self.data.refresh_ids(self, [book_id])
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
@ -1300,7 +1326,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)',
|
obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)',
|
||||||
(mi.title, mi.authors[0]))
|
(mi.title, mi.authors[0]))
|
||||||
id = obj.lastrowid
|
id = obj.lastrowid
|
||||||
self.data.books_added([id], self.conn)
|
self.data.books_added([id], self)
|
||||||
self.set_path(id, index_is_id=True)
|
self.set_path(id, index_is_id=True)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.set_metadata(id, mi)
|
self.set_metadata(id, mi)
|
||||||
@ -1309,7 +1335,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
if not hasattr(path, 'read'):
|
if not hasattr(path, 'read'):
|
||||||
stream.close()
|
stream.close()
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
|
self.data.refresh_ids(self, [id]) # Needed to update format list and size
|
||||||
return id
|
return id
|
||||||
|
|
||||||
def run_import_plugins(self, path_or_stream, format):
|
def run_import_plugins(self, path_or_stream, format):
|
||||||
@ -1337,7 +1363,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
||||||
(title, series_index, aus))
|
(title, series_index, aus))
|
||||||
id = obj.lastrowid
|
id = obj.lastrowid
|
||||||
self.data.books_added([id], self.conn)
|
self.data.books_added([id], self)
|
||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.set_metadata(id, mi)
|
self.set_metadata(id, mi)
|
||||||
@ -1370,7 +1396,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
||||||
(title, series_index, aus))
|
(title, series_index, aus))
|
||||||
id = obj.lastrowid
|
id = obj.lastrowid
|
||||||
self.data.books_added([id], self.conn)
|
self.data.books_added([id], self)
|
||||||
ids.append(id)
|
ids.append(id)
|
||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -1381,7 +1407,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
self.add_format(id, format, stream, index_is_id=True)
|
self.add_format(id, format, stream, index_is_id=True)
|
||||||
stream.close()
|
stream.close()
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.refresh_ids(self.conn, ids) # Needed to update format list and size
|
self.data.refresh_ids(self, ids) # Needed to update format list and size
|
||||||
if duplicates:
|
if duplicates:
|
||||||
paths = list(duplicate[0] for duplicate in duplicates)
|
paths = list(duplicate[0] for duplicate in duplicates)
|
||||||
formats = list(duplicate[1] for duplicate in duplicates)
|
formats = list(duplicate[1] for duplicate in duplicates)
|
||||||
@ -1403,7 +1429,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
||||||
(title, series_index, aus))
|
(title, series_index, aus))
|
||||||
id = obj.lastrowid
|
id = obj.lastrowid
|
||||||
self.data.books_added([id], self.conn)
|
self.data.books_added([id], self)
|
||||||
self.set_path(id, True)
|
self.set_path(id, True)
|
||||||
self.set_metadata(id, mi)
|
self.set_metadata(id, mi)
|
||||||
for path in formats:
|
for path in formats:
|
||||||
@ -1412,7 +1438,7 @@ class LibraryDatabase2(LibraryDatabase):
|
|||||||
continue
|
continue
|
||||||
self.add_format_with_hooks(id, ext, path, index_is_id=True)
|
self.add_format_with_hooks(id, ext, path, index_is_id=True)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size
|
self.data.refresh_ids(self, [id]) # Needed to update format list and size
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('add', [id])
|
self.notify('add', [id])
|
||||||
|
|
||||||
|
@ -548,6 +548,10 @@ def _prefs():
|
|||||||
help=_('The language in which to display the user interface'))
|
help=_('The language in which to display the user interface'))
|
||||||
c.add_opt('output_format', default='EPUB',
|
c.add_opt('output_format', default='EPUB',
|
||||||
help=_('The default output format for ebook conversions.'))
|
help=_('The default output format for ebook conversions.'))
|
||||||
|
c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'LIT', 'PRC',
|
||||||
|
'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ODT', 'RTF', 'PDF',
|
||||||
|
'TXT'],
|
||||||
|
help=_('Ordered list of formats to prefer for input.'))
|
||||||
c.add_opt('read_file_metadata', default=True,
|
c.add_opt('read_file_metadata', default=True,
|
||||||
help=_('Read metadata from files'))
|
help=_('Read metadata from files'))
|
||||||
c.add_opt('worker_process_priority', default='normal',
|
c.add_opt('worker_process_priority', default='normal',
|
||||||
|
@ -50,6 +50,8 @@ class SearchQueryParser(object):
|
|||||||
'author',
|
'author',
|
||||||
'publisher',
|
'publisher',
|
||||||
'series',
|
'series',
|
||||||
|
'rating',
|
||||||
|
'cover',
|
||||||
'comments',
|
'comments',
|
||||||
'format',
|
'format',
|
||||||
'isbn',
|
'isbn',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user