Present the APNX page count method as a combobox to the user

Merge branch 'device_config' of https://github.com/user-none/calibre
This commit is contained in:
Kovid Goyal 2014-05-25 10:57:26 +05:30
commit e302d61e28
4 changed files with 94 additions and 76 deletions

View File

@ -69,7 +69,7 @@ class APNXBuilder(object):
if not pages: if not pages:
pages = self.get_pages_accurate(mobi_file_path) pages = self.get_pages_accurate(mobi_file_path)
else: else:
raise('no valid accurate method chosen use fast') raise Exception('%r is not a valid apnx generation method' % method)
except: except:
# Fall back to the fast parser if we can't # Fall back to the fast parser if we can't
# use the accurate one. Typically this is # use the accurate one. Typically this is

View File

@ -83,7 +83,6 @@ class KINDLE(USBMS):
'replace') 'replace')
return mi return mi
def get_annotations(self, path_map): def get_annotations(self, path_map):
MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt'] MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt']
mbp_formats = set(MBP_FORMATS) mbp_formats = set(MBP_FORMATS)
@ -182,16 +181,16 @@ class KINDLE(USBMS):
spanTag['style'] = 'font-weight:bold' spanTag['style'] = 'font-weight:bold'
if bookmark.book_format == 'pdf': if bookmark.book_format == 'pdf':
spanTag.insert(0,NavigableString( spanTag.insert(0,NavigableString(
_("%(time)s<br />Last Page Read: %(loc)d (%(pr)d%%)") % \ _("%(time)s<br />Last Page Read: %(loc)d (%(pr)d%%)") % dict(
dict(time=strftime(u'%x', timestamp.timetuple()), time=strftime(u'%x', timestamp.timetuple()),
loc=last_read_location, loc=last_read_location,
pr=percent_read))) pr=percent_read)))
else: else:
spanTag.insert(0,NavigableString( spanTag.insert(0,NavigableString(
_("%(time)s<br />Last Page Read: Location %(loc)d (%(pr)d%%)") % \ _("%(time)s<br />Last Page Read: Location %(loc)d (%(pr)d%%)") % dict(
dict(time=strftime(u'%x', timestamp.timetuple()), time=strftime(u'%x', timestamp.timetuple()),
loc=last_read_location, loc=last_read_location,
pr=percent_read))) pr=percent_read)))
divTag.insert(dtc, spanTag) divTag.insert(dtc, spanTag)
dtc += 1 dtc += 1
@ -207,23 +206,23 @@ class KINDLE(USBMS):
for location in sorted(user_notes): for location in sorted(user_notes):
if user_notes[location]['text']: if user_notes[location]['text']:
annotations.append( annotations.append(
_('<b>Location %(dl)d &bull; %(typ)s</b><br />%(text)s<br />') % \ _('<b>Location %(dl)d &bull; %(typ)s</b><br />%(text)s<br />') % dict(
dict(dl=user_notes[location]['displayed_location'], dl=user_notes[location]['displayed_location'],
typ=user_notes[location]['type'], typ=user_notes[location]['type'],
text=(user_notes[location]['text'] if \ text=(user_notes[location]['text'] if
user_notes[location]['type'] == 'Note' else \ user_notes[location]['type'] == 'Note' else
'<i>%s</i>' % user_notes[location]['text']))) '<i>%s</i>' % user_notes[location]['text'])))
else: else:
if bookmark.book_format == 'pdf': if bookmark.book_format == 'pdf':
annotations.append( annotations.append(
_('<b>Page %(dl)d &bull; %(typ)s</b><br />') % \ _('<b>Page %(dl)d &bull; %(typ)s</b><br />') % dict(
dict(dl=user_notes[location]['displayed_location'], dl=user_notes[location]['displayed_location'],
typ=user_notes[location]['type'])) typ=user_notes[location]['type']))
else: else:
annotations.append( annotations.append(
_('<b>Location %(dl)d &bull; %(typ)s</b><br />') % \ _('<b>Location %(dl)d &bull; %(typ)s</b><br />') % dict(
dict(dl=user_notes[location]['displayed_location'], dl=user_notes[location]['displayed_location'],
typ=user_notes[location]['type'])) typ=user_notes[location]['type']))
for annotation in annotations: for annotation in annotations:
divTag.insert(dtc, annotation) divTag.insert(dtc, annotation)
@ -232,7 +231,6 @@ class KINDLE(USBMS):
ka_soup.insert(0,divTag) ka_soup.insert(0,divTag)
return ka_soup return ka_soup
def add_annotation_to_library(self, db, db_id, annotation): def add_annotation_to_library(self, db, db_id, annotation):
from calibre.ebooks.BeautifulSoup import Tag from calibre.ebooks.BeautifulSoup import Tag
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -278,7 +276,7 @@ class KINDLE(USBMS):
mi.comments = last_update mi.comments = last_update
db.set_metadata(mc_id[0], mi) db.set_metadata(mc_id[0], mi)
else: else:
mi = MetaInformation('My Clippings', authors = ['Kindle']) mi = MetaInformation('My Clippings', authors=['Kindle'])
mi.tags = ['Clippings'] mi.tags = ['Clippings']
mi.comments = last_update mi.comments = last_update
db.add_books([bm.value['path']], ['txt'], [mi]) db.add_books([bm.value['path']], ['txt'], [mi])
@ -290,7 +288,9 @@ class KINDLE2(KINDLE):
FORMATS = ['azw', 'mobi', 'azw3', 'prc', 'azw1', 'tpz', 'azw4', 'pobi', 'pdf', 'txt'] FORMATS = ['azw', 'mobi', 'azw3', 'prc', 'azw1', 'tpz', 'azw4', 'pobi', 'pdf', 'txt']
DELETE_EXTS = KINDLE.DELETE_EXTS + ['.mbp1', '.mbs', '.sdr', '.han'] DELETE_EXTS = KINDLE.DELETE_EXTS + ['.mbp1', '.mbs', '.sdr', '.han']
# On the Touch, there's also .asc files, but not using the same basename (for X-Ray & End Actions), azw3f & azw3r files, but all of them are in the .sdr sidecar folder # On the Touch, there's also .asc files, but not using the same basename
# (for X-Ray & End Actions), azw3f & azw3r files, but all of them are in
# the .sdr sidecar folder
PRODUCT_ID = [0x0002, 0x0004] PRODUCT_ID = [0x0002, 0x0004]
BCD = [0x0100] BCD = [0x0100]
@ -298,49 +298,54 @@ class KINDLE2(KINDLE):
# SUPPORTS_SUB_DIRS_FOR_SCAN = True # SUPPORTS_SUB_DIRS_FOR_SCAN = True
EXTRA_CUSTOMIZATION_MESSAGE = [ EXTRA_CUSTOMIZATION_MESSAGE = [
_('Send page number information when sending books') + _('Send page number information when sending books') + ':::' + _(
':::' + 'The Kindle 3 and newer versions can use page number information'
_('The Kindle 3 and newer versions can use page number information ' ' in MOBI files. With this option, calibre will calculate and send'
'in MOBI files. With this option, calibre will calculate and send' ' this information to the Kindle when uploading MOBI files by'
' this information to the Kindle when uploading MOBI files by' ' USB. Note that the page numbers do not correspond to any paper'
' USB. Note that the page numbers do not correspond to any paper' ' book.'),
' book.'), _('Page count calculation method') + ':::' + '<p>' + _(
_('Use slower but more accurate page number calculation') + 'There are multiple ways to generate the page number information.'
':::' + ' If a page count is given then the book will be divided into that many pages.'
_('There are two ways to generate the page number information. Using the more accurate ' ' Otherwise the number of pages will be approximated using one of the following'
'generator will produce pages that correspond better to a printed book. ' ' methods.<ul>'
'However, this method is slower and will slow down sending files ' ' <li>fast: 2300 characters of uncompressed text per page.\n\n'
'to the Kindle.'), ' <li>accurate: Based on the number of chapters, paragraphs, and visible lines in the book.'
_('Accurate calculation method') + ' This method is designed to simulate an average paperback book where there are 32 lines per'
':::' + ' page and a maximum of 70 characters per line.\n\n'
_('There are multiple methods to accurately calculate the page numbers. "accurate" which ' ' <li>pagebreak: The "pagebreak" method uses the presense of <mbp:pagebreak> tags within'
'is an estimation based on the number of chapters, paragraphs, and visible lines in the book. ' ' the book to determine pages.</ul>'
'This method is designed to simulate an average paperback book where there are 32 lines per ' 'Methods other than "fast" are going to be much slower.'
'page and a maximum of 70 characters per line. \n\n' ' Further, if "pagebreak" fails to determine a page count accurate will be used, and if '
'The "pagebreak" method uses the presense of <mbp:pagebreak> tags within the book to ' ' "accurate" fails fast will be used.'),
'determine pages.'), _('Custom column name to retrieve page counts from') + ':::' + _(
_('Custom column name to retrieve page counts from') + 'If you have a custom column in your library that you use to'
':::' + ' store the page count of books, you can have calibre use that'
_('If you have a custom column in your library that you use to ' ' information, instead of calculating a page count. Specify the'
'store the page count of books, you can have calibre use that ' ' name of the custom column here, for example, #pages.'),
'information, instead of calculating a page count. Specify the '
'name of the custom column here, for example, #pages. '),
] ]
EXTRA_CUSTOMIZATION_DEFAULT = [ EXTRA_CUSTOMIZATION_DEFAULT = [
True, True,
False, 'fast',
'accurate',
'', '',
] ]
OPT_APNX = 0 OPT_APNX = 0
OPT_APNX_ACCURATE = 1 OPT_APNX_METHOD = 1
OPT_APNX_ACCURATE_METHOD = 2 OPT_APNX_CUST_COL = 2
OPT_APNX_CUST_COL = 3 EXTRA_CUSTOMIZATION_CHOICES = {OPT_APNX_METHOD:{'fast', 'accurate', 'pagebreak'}}
# x330 on the PaperWhite # x330 on the PaperWhite
THUMBNAIL_HEIGHT = 330 THUMBNAIL_HEIGHT = 330
# x262 on the Touch. Doesn't choke on x330, though. # x262 on the Touch. Doesn't choke on x330, though.
@classmethod
def migrate_extra_customization(cls, vals):
if isinstance(vals[cls.OPT_APNX_METHOD], bool):
# Previously this option used to be a bool
vals[cls.OPT_APNX_METHOD] = 'accurate' if vals[cls.OPT_APNX_METHOD] else 'fast'
return vals
def formats_to_scan_for(self): def formats_to_scan_for(self):
ans = USBMS.formats_to_scan_for(self) | {'azw3'} ans = USBMS.formats_to_scan_for(self) | {'azw3'}
return ans return ans
@ -408,7 +413,8 @@ class KINDLE2(KINDLE):
if not coverdata or not coverdata[2]: if not coverdata or not coverdata[2]:
return return
thumb_dir = os.path.join(self._main_prefix, 'system', 'thumbnails') thumb_dir = os.path.join(self._main_prefix, 'system', 'thumbnails')
if not os.path.exists(thumb_dir): return if not os.path.exists(thumb_dir):
return
from calibre.ebooks.mobi.reader.headers import MetadataHeader from calibre.ebooks.mobi.reader.headers import MetadataHeader
with lopen(filepath, 'rb') as f: with lopen(filepath, 'rb') as f:
@ -451,12 +457,8 @@ class KINDLE2(KINDLE):
apnx_path = '%s.apnx' % os.path.join(path, filename) apnx_path = '%s.apnx' % os.path.join(path, filename)
apnx_builder = APNXBuilder() apnx_builder = APNXBuilder()
try: try:
method = None method = opts.extra_customization[self.OPT_APNX_METHOD]
if opts.extra_customization[self.OPT_APNX_ACCURATE]: apnx_builder.write_apnx(filepath, apnx_path, method=method, page_count=custom_page_count)
method = opts.extra_customization[self.OPT_APNX_ACCURATE_METHOD]
apnx_builder.write_apnx(filepath, apnx_path,
method=method,
page_count=custom_page_count)
except: except:
print 'Failed to generate APNX' print 'Failed to generate APNX'
import traceback import traceback

View File

@ -27,9 +27,13 @@ class DeviceConfig(object):
#: EXTRA_CUSTOMIZATION_MESSAGE you *must* set this as well. #: EXTRA_CUSTOMIZATION_MESSAGE you *must* set this as well.
EXTRA_CUSTOMIZATION_DEFAULT = None EXTRA_CUSTOMIZATION_DEFAULT = None
#: A dictionary providing choices for options that should be displayed as a
#: combo box to the user. The dictionary maps extra #: customization indexes
#: to a set of choices.
EXTRA_CUSTOMIZATION_CHOICES = None
SUPPORTS_SUB_DIRS = False SUPPORTS_SUB_DIRS = False
SUPPORTS_SUB_DIRS_FOR_SCAN = False # This setting is used when scanning for SUPPORTS_SUB_DIRS_FOR_SCAN = False # This setting is used when scanning for books when SUPPORTS_SUB_DIRS is False
# books when SUPPORTS_SUB_DIRS is False
SUPPORTS_SUB_DIRS_DEFAULT = True SUPPORTS_SUB_DIRS_DEFAULT = True
MUST_READ_METADATA = False MUST_READ_METADATA = False
@ -41,7 +45,6 @@ class DeviceConfig(object):
#: If True the user can add new formats to the driver #: If True the user can add new formats to the driver
USER_CAN_ADD_NEW_FORMATS = True USER_CAN_ADD_NEW_FORMATS = True
@classmethod @classmethod
def _default_save_template(cls): def _default_save_template(cls):
from calibre.library.save_to_disk import config from calibre.library.save_to_disk import config
@ -81,7 +84,7 @@ class DeviceConfig(object):
from calibre.gui2.device_drivers.configwidget import ConfigWidget from calibre.gui2.device_drivers.configwidget import ConfigWidget
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS, cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT, cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
cls.EXTRA_CUSTOMIZATION_MESSAGE, cls) cls.EXTRA_CUSTOMIZATION_MESSAGE, cls, extra_customization_choices=cls.EXTRA_CUSTOMIZATION_CHOICES)
return cw return cw
@classmethod @classmethod
@ -103,6 +106,8 @@ class DeviceConfig(object):
continue continue
if hasattr(config_widget.opt_extra_customization[i], 'isChecked'): if hasattr(config_widget.opt_extra_customization[i], 'isChecked'):
ec.append(config_widget.opt_extra_customization[i].isChecked()) ec.append(config_widget.opt_extra_customization[i].isChecked())
elif hasattr(config_widget.opt_extra_customization[i], 'currentText'):
ec.append(unicode(config_widget.opt_extra_customization[i].currentText()).strip())
else: else:
ec.append(unicode(config_widget.opt_extra_customization[i].text()).strip()) ec.append(unicode(config_widget.opt_extra_customization[i].text()).strip())
else: else:
@ -113,6 +118,10 @@ class DeviceConfig(object):
st = unicode(config_widget.opt_save_template.text()) st = unicode(config_widget.opt_save_template.text())
proxy['save_template'] = st proxy['save_template'] = st
@classmethod
def migrate_extra_customization(cls, vals):
return vals
@classmethod @classmethod
def settings(cls): def settings(cls):
opts = cls._config().parse() opts = cls._config().parse()
@ -121,9 +130,7 @@ class DeviceConfig(object):
opts.extra_customization = [] opts.extra_customization = []
if not isinstance(opts.extra_customization, list): if not isinstance(opts.extra_customization, list):
opts.extra_customization = [opts.extra_customization] opts.extra_customization = [opts.extra_customization]
for i,d in enumerate(cls.EXTRA_CUSTOMIZATION_DEFAULT): opts.extra_customization = cls.migrate_extra_customization(opts.extra_customization)
if i >= len(opts.extra_customization):
opts.extra_customization.append(d)
return opts return opts
@classmethod @classmethod

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
import textwrap import textwrap
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QVariant, QLabel, from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QVariant, QLabel,
QLineEdit, QCheckBox) QLineEdit, QCheckBox, QComboBox)
from calibre.gui2 import error_dialog, question_dialog from calibre.gui2 import error_dialog, question_dialog
from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
@ -18,7 +18,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
def __init__(self, settings, all_formats, supports_subdirs, def __init__(self, settings, all_formats, supports_subdirs,
must_read_metadata, supports_use_author_sort, must_read_metadata, supports_use_author_sort,
extra_customization_message, device): extra_customization_message, device, extra_customization_choices=None):
QWidget.__init__(self) QWidget.__init__(self)
Ui_ConfigWidget.__init__(self) Ui_ConfigWidget.__init__(self)
@ -62,6 +62,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
else: else:
self.opt_use_author_sort.hide() self.opt_use_author_sort.hide()
if extra_customization_message: if extra_customization_message:
extra_customization_choices = extra_customization_choices or {}
def parse_msg(m): def parse_msg(m):
msg, _, tt = m.partition(':::') if m else ('', '', '') msg, _, tt = m.partition(':::') if m else ('', '', '')
return msg.strip(), textwrap.fill(tt.strip(), 100) return msg.strip(), textwrap.fill(tt.strip(), 100)
@ -84,6 +85,14 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
self.opt_extra_customization.append(QCheckBox(label_text)) self.opt_extra_customization.append(QCheckBox(label_text))
self.opt_extra_customization[-1].setToolTip(tt) self.opt_extra_customization[-1].setToolTip(tt)
self.opt_extra_customization[i].setChecked(bool(settings.extra_customization[i])) self.opt_extra_customization[i].setChecked(bool(settings.extra_customization[i]))
elif i in extra_customization_choices:
cb = QComboBox(self)
self.opt_extra_customization.append(cb)
l = QLabel(label_text)
l.setToolTip(tt), cb.setToolTip(tt), l.setBuddy(cb), cb.setToolTip(tt)
for li in sorted(extra_customization_choices[i]):
self.opt_extra_customization[i].addItem(li)
cb.setCurrentIndex(max(0, cb.findText(settings.extra_customization[i])))
else: else:
self.opt_extra_customization.append(QLineEdit(self)) self.opt_extra_customization.append(QLineEdit(self))
l = QLabel(label_text) l = QLabel(label_text)
@ -111,7 +120,6 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
self.extra_layout.addWidget(self.opt_extra_customization, 1, 0) self.extra_layout.addWidget(self.opt_extra_customization, 1, 0)
self.opt_save_template.setText(settings.save_template) self.opt_save_template.setText(settings.save_template)
def up_column(self): def up_column(self):
idx = self.columns.currentRow() idx = self.columns.currentRow()
if idx > 0: if idx > 0:
@ -125,7 +133,8 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
self.columns.setCurrentRow(idx+1) self.columns.setCurrentRow(idx+1)
def format_map(self): def format_map(self):
formats = [unicode(self.columns.item(i).data(Qt.UserRole).toString()) for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked] formats = [unicode(self.columns.item(i).data(Qt.UserRole).toString())
for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked]
return formats return formats
def use_subdirs(self): def use_subdirs(self):
@ -156,7 +165,7 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
return True return True
except Exception as err: except Exception as err:
error_dialog(self, _('Invalid template'), error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl + \ '<p>'+_('The template %s is invalid:')%tmpl +
'<br>'+unicode(err), show=True) '<br>'+unicode(err), show=True)
return False return False