mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Kobo driver: Improved configuration dialog for the Kobo Touch device driver
This commit is contained in:
commit
dcc2e3bc26
@ -182,7 +182,7 @@ def print_basic_debug_info(out=None):
|
|||||||
pass
|
pass
|
||||||
from calibre.customize.ui import has_external_plugins, initialized_plugins
|
from calibre.customize.ui import has_external_plugins, initialized_plugins
|
||||||
if has_external_plugins():
|
if has_external_plugins():
|
||||||
names = (p.name for p in initialized_plugins() if getattr(p, 'plugin_path', None) is not None)
|
names = ('{0} {1}'.format(p.name, p.version) for p in initialized_plugins() if getattr(p, 'plugin_path', None) is not None)
|
||||||
out('Successfully initialized third party plugins:', ' && '.join(names))
|
out('Successfully initialized third party plugins:', ' && '.join(names))
|
||||||
|
|
||||||
def run_debug_gui(logpath):
|
def run_debug_gui(logpath):
|
||||||
|
@ -145,6 +145,11 @@ class KOBO(USBMS):
|
|||||||
OPT_SHOW_RECOMMENDATIONS = 5
|
OPT_SHOW_RECOMMENDATIONS = 5
|
||||||
OPT_SUPPORT_NEWER_FIRMWARE = 6
|
OPT_SUPPORT_NEWER_FIRMWARE = 6
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
USBMS.__init__(self, *args, **kwargs)
|
||||||
|
self.plugboards = self.plugboard_func = None
|
||||||
|
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
USBMS.initialize(self)
|
USBMS.initialize(self)
|
||||||
self.dbversion = 7
|
self.dbversion = 7
|
||||||
@ -311,7 +316,7 @@ class KOBO(USBMS):
|
|||||||
'BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)'
|
'BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)'
|
||||||
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')',
|
if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')',
|
||||||
previews=' and Accessibility <> 6'
|
previews=' and Accessibility <> 6'
|
||||||
if opts.extra_customization[self.OPT_SHOW_PREVIEWS] == False else '',
|
if not self.show_previews else '',
|
||||||
recomendations=' and IsDownloaded in (\'true\', 1)'
|
recomendations=' and IsDownloaded in (\'true\', 1)'
|
||||||
if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] == False else '')
|
if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] == False else '')
|
||||||
elif self.dbversion >= 16 and self.dbversion < 33:
|
elif self.dbversion >= 16 and self.dbversion < 33:
|
||||||
@ -709,7 +714,7 @@ class KOBO(USBMS):
|
|||||||
# debug_print("KOBO:book_from_path - title=%s"%title)
|
# debug_print("KOBO:book_from_path - title=%s"%title)
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
|
|
||||||
if cls.settings().read_metadata or cls.MUST_READ_METADATA:
|
if cls.read_metadata or cls.MUST_READ_METADATA:
|
||||||
mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
|
mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath)))
|
||||||
else:
|
else:
|
||||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||||
@ -898,12 +903,15 @@ class KOBO(USBMS):
|
|||||||
# debug_print('Finished update_device_database_collections', collections_attributes)
|
# debug_print('Finished update_device_database_collections', collections_attributes)
|
||||||
|
|
||||||
def get_collections_attributes(self):
|
def get_collections_attributes(self):
|
||||||
collections = []
|
collections = [x.lower().strip() for x in self.collections_columns.split(',')]
|
||||||
opts = self.settings()
|
|
||||||
if opts.extra_customization and len(opts.extra_customization[self.OPT_COLLECTIONS]) > 0:
|
|
||||||
collections = [x.lower().strip() for x in opts.extra_customization[self.OPT_COLLECTIONS].split(',')]
|
|
||||||
return collections
|
return collections
|
||||||
|
|
||||||
|
@property
|
||||||
|
def collections_columns(self):
|
||||||
|
opts = self.settings()
|
||||||
|
return opts.extra_customization[self.OPT_COLLECTIONS]
|
||||||
|
|
||||||
|
|
||||||
def sync_booklists(self, booklists, end_session=True):
|
def sync_booklists(self, booklists, end_session=True):
|
||||||
debug_print('KOBO:sync_booklists - start')
|
debug_print('KOBO:sync_booklists - start')
|
||||||
paths = self.get_device_paths()
|
paths = self.get_device_paths()
|
||||||
@ -1045,6 +1053,50 @@ class KOBO(USBMS):
|
|||||||
paths[idx] = tf.name
|
paths[idx] = tf.name
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def config_widget(self):
|
||||||
|
# TODO: Cleanup the following
|
||||||
|
self.current_friendly_name = self.gui_name
|
||||||
|
|
||||||
|
from calibre.gui2.device_drivers.tabbed_device_config import TabbedDeviceConfig
|
||||||
|
return TabbedDeviceConfig(self.settings(), self.FORMATS, self.SUPPORTS_SUB_DIRS,
|
||||||
|
self.MUST_READ_METADATA, self.SUPPORTS_USE_AUTHOR_SORT,
|
||||||
|
self.EXTRA_CUSTOMIZATION_MESSAGE, self,
|
||||||
|
extra_customization_choices=self.EXTRA_CUSTOMIZATION_CHOICES)
|
||||||
|
|
||||||
|
def migrate_old_settings(self, old_settings):
|
||||||
|
|
||||||
|
OPT_COLLECTIONS = 0
|
||||||
|
OPT_UPLOAD_COVERS = 1
|
||||||
|
OPT_UPLOAD_GRAYSCALE_COVERS = 2
|
||||||
|
OPT_SHOW_EXPIRED_BOOK_RECORDS = 3
|
||||||
|
OPT_SHOW_PREVIEWS = 4
|
||||||
|
OPT_SHOW_RECOMMENDATIONS = 5
|
||||||
|
OPT_SUPPORT_NEWER_FIRMWARE = 6
|
||||||
|
|
||||||
|
p = {}
|
||||||
|
p['format_map'] = old_settings.format_map
|
||||||
|
p['save_template'] = old_settings.save_template
|
||||||
|
p['use_subdirs'] = old_settings.use_subdirs
|
||||||
|
p['read_metadata'] = old_settings.read_metadata
|
||||||
|
p['use_author_sort'] = old_settings.use_author_sort
|
||||||
|
p['extra_customization'] = old_settings.extra_customization
|
||||||
|
|
||||||
|
p['collections_columns'] = old_settings.extra_customization[OPT_COLLECTIONS]
|
||||||
|
|
||||||
|
p['upload_covers'] = old_settings.extra_customization[OPT_UPLOAD_COVERS]
|
||||||
|
p['upload_grayscale'] = old_settings.extra_customization[OPT_UPLOAD_GRAYSCALE_COVERS]
|
||||||
|
|
||||||
|
p['show_expired_books'] = old_settings.extra_customization[OPT_SHOW_EXPIRED_BOOK_RECORDS]
|
||||||
|
p['show_previews'] = old_settings.extra_customization[OPT_SHOW_PREVIEWS]
|
||||||
|
p['show_recommendations'] = old_settings.extra_customization[OPT_SHOW_RECOMMENDATIONS]
|
||||||
|
|
||||||
|
p['support_newer_firmware'] = old_settings.extra_customization[OPT_SUPPORT_NEWER_FIRMWARE]
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_annotations_path(self, mdata, device_path=None):
|
def create_annotations_path(self, mdata, device_path=None):
|
||||||
if device_path:
|
if device_path:
|
||||||
return device_path
|
return device_path
|
||||||
@ -1286,83 +1338,9 @@ class KOBOTOUCH(KOBO):
|
|||||||
KOBO_EXTRA_CSSFILE = 'kobo_extra.css'
|
KOBO_EXTRA_CSSFILE = 'kobo_extra.css'
|
||||||
|
|
||||||
EXTRA_CUSTOMIZATION_MESSAGE = [
|
EXTRA_CUSTOMIZATION_MESSAGE = [
|
||||||
_('The Kobo from firmware V2.0.0 supports bookshelves.'
|
]
|
||||||
' These are created on the Kobo. ' +
|
|
||||||
'Specify a tags type column for automatic management.'),
|
|
||||||
_('Create Bookshelves') +
|
|
||||||
':::'+_('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.'),
|
|
||||||
_('Delete Empty Bookshelves') +
|
|
||||||
':::'+_('Delete any empty bookshelves from the Kobo when syncing is finished. This is only for firmware V2.0.0 or later.'),
|
|
||||||
_('Upload covers for books') +
|
|
||||||
':::'+_('Upload cover images from the calibre library when sending books to the device.'),
|
|
||||||
_('Upload Black and White Covers'),
|
|
||||||
_('Keep cover aspect ratio') +
|
|
||||||
':::'+_('When uploading covers, do not change the aspect ratio when resizing for the device.'
|
|
||||||
' This is for firmware versions 2.3.1 and later.'),
|
|
||||||
_('Show archived books') +
|
|
||||||
':::'+_('Archived books are listed on the device but need to be downloaded to read.'
|
|
||||||
' Use this option to show these books and match them with books in the calibre library.'),
|
|
||||||
_('Show Previews') +
|
|
||||||
':::'+_('Kobo previews are included on the Touch and some other versions'
|
|
||||||
' by default they are no longer displayed as there is no good reason to '
|
|
||||||
'see them. Enable if you wish to see/delete them.'),
|
|
||||||
_('Show Recommendations') +
|
|
||||||
':::'+_('Kobo shows recommendations on the device. In some cases these have '
|
|
||||||
'files but in other cases they are just pointers to the web site to buy. '
|
|
||||||
'Enable if you wish to see/delete them.'),
|
|
||||||
_('Set Series information') +
|
|
||||||
':::'+_('The book lists on the Kobo devices can display series information. '
|
|
||||||
'This is not read by the device from the sideloaded books. '
|
|
||||||
'Series information can only be added to the device after the book has been processed by the device. '
|
|
||||||
'Enable if you wish to set series information.'),
|
|
||||||
_('Modify CSS') +
|
|
||||||
':::'+_('This allows addition of user CSS rules and removal of some CSS. '
|
|
||||||
'When sending a book, the driver adds the contents of {0} to all stylesheets in the ePub. '
|
|
||||||
'This file is searched for in the root directory of the main memory of the device. '
|
|
||||||
'As well as this, if the file contains settings for the "orphans" or "widows", '
|
|
||||||
'these are removed for all styles in the original stylesheet.').format(KOBO_EXTRA_CSSFILE),
|
|
||||||
_('Attempt to support newer firmware') +
|
|
||||||
':::'+_('Kobo routinely updates the firmware and the '
|
|
||||||
'database version. With this option Calibre will attempt '
|
|
||||||
'to perform full read-write functionality - Here be Dragons!! '
|
|
||||||
'Enable only if you are comfortable with restoring your kobo '
|
|
||||||
'to factory defaults and testing software. '
|
|
||||||
'This driver supports firmware V2.x.x and DBVersion up to ') + unicode(supported_dbversion),
|
|
||||||
_('Title to test when debugging') +
|
|
||||||
':::'+_('Part of title of a book that can be used when doing some tests for debugging. '
|
|
||||||
'The test is to see if the string is contained in the title of a book. '
|
|
||||||
'The better the match, the less extraneous output.'),
|
|
||||||
]
|
|
||||||
|
|
||||||
EXTRA_CUSTOMIZATION_DEFAULT = [
|
EXTRA_CUSTOMIZATION_DEFAULT = [
|
||||||
u'',
|
]
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
False,
|
|
||||||
u''
|
|
||||||
]
|
|
||||||
|
|
||||||
OPT_COLLECTIONS = 0
|
|
||||||
OPT_CREATE_BOOKSHELVES = 1
|
|
||||||
OPT_DELETE_BOOKSHELVES = 2
|
|
||||||
OPT_UPLOAD_COVERS = 3
|
|
||||||
OPT_UPLOAD_GRAYSCALE_COVERS = 4
|
|
||||||
OPT_KEEP_COVER_ASPECT_RATIO = 5
|
|
||||||
OPT_SHOW_ARCHIVED_BOOK_RECORDS = 6
|
|
||||||
OPT_SHOW_PREVIEWS = 7
|
|
||||||
OPT_SHOW_RECOMMENDATIONS = 8
|
|
||||||
OPT_UPDATE_SERIES_DETAILS = 9
|
|
||||||
OPT_MODIFY_CSS = 10
|
|
||||||
OPT_SUPPORT_NEWER_FIRMWARE = 11
|
|
||||||
OPT_DEBUGGING_TITLE = 12
|
|
||||||
|
|
||||||
opts = None
|
opts = None
|
||||||
|
|
||||||
@ -1415,6 +1393,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
# ' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver if "Full screen" is checked.
|
# ' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver if "Full screen" is checked.
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
KOBO.__init__(self, *args, **kwargs)
|
||||||
|
self.plugboards = self.plugboard_func = None
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
super(KOBOTOUCH, self).initialize()
|
super(KOBOTOUCH, self).initialize()
|
||||||
self.bookshelvelist = []
|
self.bookshelvelist = []
|
||||||
@ -1426,6 +1408,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
def books(self, oncard=None, end_session=True):
|
def books(self, oncard=None, end_session=True):
|
||||||
debug_print("KoboTouch:books - oncard='%s'"%oncard)
|
debug_print("KoboTouch:books - oncard='%s'"%oncard)
|
||||||
from calibre.ebooks.metadata.meta import path_to_ext
|
from calibre.ebooks.metadata.meta import path_to_ext
|
||||||
|
self.debugging_title = self.get_debugging_title()
|
||||||
|
|
||||||
dummy_bl = self.booklist_class(None, None, None)
|
dummy_bl = self.booklist_class(None, None, None)
|
||||||
|
|
||||||
@ -1467,11 +1450,11 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
opts = self.settings()
|
opts = self.settings()
|
||||||
debug_print("KoboTouch:books - opts.extra_customization=", opts.extra_customization)
|
debug_print("KoboTouch:books - opts.extra_customization=", opts.extra_customization)
|
||||||
|
debug_print("KoboTouch:books - driver options=", self)
|
||||||
debug_print("KoboTouch:books - prefs['manage_device_metadata']=", prefs['manage_device_metadata'])
|
debug_print("KoboTouch:books - prefs['manage_device_metadata']=", prefs['manage_device_metadata'])
|
||||||
if opts.extra_customization:
|
debugging_title = self.debugging_title
|
||||||
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
debug_print("KoboTouch:books - set_debugging_title to '%s'" % debugging_title)
|
||||||
debug_print("KoboTouch:books - set_debugging_title to '%s'" % debugging_title)
|
bl.set_debugging_title(debugging_title)
|
||||||
bl.set_debugging_title(debugging_title)
|
|
||||||
debug_print("KoboTouch:books - length bl=%d"%len(bl))
|
debug_print("KoboTouch:books - length bl=%d"%len(bl))
|
||||||
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
||||||
debug_print("KoboTouch:books - length bl after sync=%d"%len(bl))
|
debug_print("KoboTouch:books - length bl after sync=%d"%len(bl))
|
||||||
@ -1658,7 +1641,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
def get_bookshelvesforbook(connection, ContentID):
|
def get_bookshelvesforbook(connection, ContentID):
|
||||||
# debug_print("KoboTouch:get_bookshelvesforbook - " + ContentID)
|
# debug_print("KoboTouch:get_bookshelvesforbook - " + ContentID)
|
||||||
bookshelves = []
|
bookshelves = []
|
||||||
if not self.supports_bookshelves():
|
if not self.supports_bookshelves:
|
||||||
return bookshelves
|
return bookshelves
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
@ -1717,32 +1700,30 @@ class KOBOTOUCH(KOBO):
|
|||||||
" %(previews)s %(recomendations)s )"
|
" %(previews)s %(recomendations)s )"
|
||||||
" and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) and ContentType = 6)") % \
|
" and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) and ContentType = 6)") % \
|
||||||
dict(
|
dict(
|
||||||
expiry="" if opts.extra_customization[self.OPT_SHOW_ARCHIVED_BOOK_RECORDS] else "and IsDownloaded in ('true', 1)",
|
expiry="" if self.show_archived_books else "and IsDownloaded in ('true', 1)",
|
||||||
previews=" or (Accessibility in (6) and ___UserID <> '')" if opts.extra_customization[self.OPT_SHOW_PREVIEWS] else "",
|
previews=" or (Accessibility in (6) and ___UserID <> '')" if self.show_previews else "",
|
||||||
recomendations=" or (Accessibility in (-1, 4, 6) and ___UserId = '')" if opts.extra_customization[
|
recomendations=" or (Accessibility in (-1, 4, 6) and ___UserId = '')" if self.show_recommendations else ""
|
||||||
self.OPT_SHOW_RECOMMENDATIONS] else ""
|
|
||||||
)
|
)
|
||||||
elif self.supports_series():
|
elif self.supports_series():
|
||||||
where_clause = (" where BookID is Null "
|
where_clause = (" where BookID is Null "
|
||||||
" and ((Accessibility = -1 and IsDownloaded in ('true', 1)) or (Accessibility in (1,2)) %(previews)s %(recomendations)s )"
|
" and ((Accessibility = -1 and IsDownloaded in ('true', 1)) or (Accessibility in (1,2)) %(previews)s %(recomendations)s )"
|
||||||
" and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)") % \
|
" and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)") % \
|
||||||
dict(
|
dict(
|
||||||
expiry=" and ContentType = 6" if opts.extra_customization[self.OPT_SHOW_ARCHIVED_BOOK_RECORDS] else "",
|
expiry=" and ContentType = 6" if self.show_archived_books else "",
|
||||||
previews=" or (Accessibility in (6) and ___UserID <> '')" if opts.extra_customization[self.OPT_SHOW_PREVIEWS] else "",
|
previews=" or (Accessibility in (6) and ___UserID <> '')" if self.show_previews else "",
|
||||||
recomendations=" or (Accessibility in (-1, 4, 6) and ___UserId = '')" if opts.extra_customization[
|
recomendations=" or (Accessibility in (-1, 4, 6) and ___UserId = '')" if self.show_recommendations else ""
|
||||||
self.OPT_SHOW_RECOMMENDATIONS] else ""
|
|
||||||
)
|
)
|
||||||
elif self.dbversion >= 33:
|
elif self.dbversion >= 33:
|
||||||
where_clause = (' where BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)') % \
|
where_clause = (' where BookID is Null %(previews)s %(recomendations)s and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)') % \
|
||||||
dict(
|
dict(
|
||||||
expiry=' and ContentType = 6' if opts.extra_customization[self.OPT_SHOW_ARCHIVED_BOOK_RECORDS] else '',
|
expiry=' and ContentType = 6' if self.show_archived_books else '',
|
||||||
previews=' and Accessibility <> 6' if opts.extra_customization[self.OPT_SHOW_PREVIEWS] == False else '',
|
previews=' and Accessibility <> 6' if not self.show_previews else '',
|
||||||
recomendations=' and IsDownloaded in (\'true\', 1)' if opts.extra_customization[self.OPT_SHOW_RECOMMENDATIONS] == False else ''
|
recomendations=' and IsDownloaded in (\'true\', 1)' if not self.show_recommendations else ''
|
||||||
)
|
)
|
||||||
elif self.dbversion >= 16:
|
elif self.dbversion >= 16:
|
||||||
where_clause = (' where BookID is Null '
|
where_clause = (' where BookID is Null '
|
||||||
'and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)') % \
|
'and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s)') % \
|
||||||
dict(expiry=' and ContentType = 6' if opts.extra_customization[self.OPT_SHOW_ARCHIVED_BOOK_RECORDS] else '')
|
dict(expiry=' and ContentType = 6' if self.show_archived_books else '')
|
||||||
else:
|
else:
|
||||||
where_clause = ' where BookID is Null'
|
where_clause = ' where BookID is Null'
|
||||||
|
|
||||||
@ -1884,6 +1865,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
def get_extra_css(self):
|
def get_extra_css(self):
|
||||||
extra_sheet = None
|
extra_sheet = None
|
||||||
|
from cssutils.css import CSSRule
|
||||||
|
|
||||||
if self.modifying_css():
|
if self.modifying_css():
|
||||||
extra_css_path = os.path.join(self._main_prefix, self.KOBO_EXTRA_CSSFILE)
|
extra_css_path = os.path.join(self._main_prefix, self.KOBO_EXTRA_CSSFILE)
|
||||||
@ -1898,8 +1880,27 @@ class KOBOTOUCH(KOBO):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print("KoboTouch:get_extra_css: Problem parsing extra CSS file {0}".format(extra_css_path))
|
debug_print("KoboTouch:get_extra_css: Problem parsing extra CSS file {0}".format(extra_css_path))
|
||||||
debug_print("KoboTouch:get_extra_css: Exception {0}".format(e))
|
debug_print("KoboTouch:get_extra_css: Exception {0}".format(e))
|
||||||
|
|
||||||
|
# create dictionary of features enabled in kobo extra css
|
||||||
|
self.extra_css_options = {}
|
||||||
|
if extra_sheet:
|
||||||
|
# search extra_css for @page rule
|
||||||
|
self.extra_css_options['has_atpage'] = len(self.get_extra_css_rules(extra_sheet, CSSRule.PAGE_RULE)) > 0
|
||||||
|
|
||||||
|
# search extra_css for style rule(s) containing widows or orphans
|
||||||
|
self.extra_css_options['has_widows_orphans'] = len(self.get_extra_css_rules_widow_orphan(extra_sheet)) > 0
|
||||||
|
debug_print('KoboTouch:get_extra_css - CSS options:', self.extra_css_options)
|
||||||
|
|
||||||
return extra_sheet
|
return extra_sheet
|
||||||
|
|
||||||
|
def get_extra_css_rules(self, sheet, css_rule):
|
||||||
|
from cssutils.css import CSSRule
|
||||||
|
return [r for r in sheet.cssRules.rulesOfType(css_rule)]
|
||||||
|
|
||||||
|
def get_extra_css_rules_widow_orphan(self, sheet):
|
||||||
|
return [r for r in self.get_extra_css_rules(sheet, CSSRule.STYLE_RULE)
|
||||||
|
if (r.style['widows'] or r.style['orphans'])]
|
||||||
|
|
||||||
def upload_books(self, files, names, on_card=None, end_session=True,
|
def upload_books(self, files, names, on_card=None, end_session=True,
|
||||||
metadata=None):
|
metadata=None):
|
||||||
debug_print('KoboTouch:upload_books - %d books'%(len(files)))
|
debug_print('KoboTouch:upload_books - %d books'%(len(files)))
|
||||||
@ -1943,7 +1944,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
self.set_filesize_in_device_database(connection, contentID, fname)
|
self.set_filesize_in_device_database(connection, contentID, fname)
|
||||||
|
|
||||||
if not self.copying_covers():
|
if not self.upload_covers:
|
||||||
imageID = self.imageid_from_contentid(contentID)
|
imageID = self.imageid_from_contentid(contentID)
|
||||||
self.delete_images(imageID, fname)
|
self.delete_images(imageID, fname)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
@ -1954,7 +1955,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _modify_epub(self, file, metadata, container=None):
|
def _modify_epub(self, book_file, metadata, container=None):
|
||||||
debug_print("KoboTouch:_modify_epub:Processing {0} - {1}".format(metadata.author_sort, metadata.title))
|
debug_print("KoboTouch:_modify_epub:Processing {0} - {1}".format(metadata.author_sort, metadata.title))
|
||||||
|
|
||||||
# Currently only modifying CSS, so if no stylesheet, don't do anything
|
# Currently only modifying CSS, so if no stylesheet, don't do anything
|
||||||
@ -1962,58 +1963,96 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print("KoboTouch:_modify_epub: no CSS file")
|
debug_print("KoboTouch:_modify_epub: no CSS file")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
commit_container = False
|
container, commit_container = self.create_container(book_file, metadata, container)
|
||||||
if not container:
|
if not container:
|
||||||
commit_container = True
|
return False
|
||||||
try:
|
|
||||||
from calibre.ebooks.oeb.polish.container import get_container
|
|
||||||
debug_print("KoboTouch:_modify_epub: creating container")
|
|
||||||
container = get_container(file)
|
|
||||||
container.css_preprocessor = DummyCSSPreProcessor()
|
|
||||||
except Exception as e:
|
|
||||||
debug_print("KoboTouch:_modify_epub: exception from get_container {0} - {1}".format(metadata.author_sort, metadata.title))
|
|
||||||
debug_print("KoboTouch:_modify_epub: exception is: {0}".format(e))
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
debug_print("KoboTouch:_modify_epub: received container")
|
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import OEB_STYLES
|
from calibre.ebooks.oeb.base import OEB_STYLES
|
||||||
|
|
||||||
|
is_dirty = False
|
||||||
for cssname, mt in container.mime_map.iteritems():
|
for cssname, mt in container.mime_map.iteritems():
|
||||||
if mt in OEB_STYLES:
|
if mt in OEB_STYLES:
|
||||||
newsheet = container.parsed(cssname)
|
newsheet = container.parsed(cssname)
|
||||||
oldrules = len(newsheet.cssRules)
|
oldrules = len(newsheet.cssRules)
|
||||||
# remove any existing @page rules in epub css
|
|
||||||
# if css to be appended contains an @page rule
|
# future css mods may be epub/kepub specific, so pass file extension arg
|
||||||
if self.extra_sheet and len([r for r in self.extra_sheet if r.type == r.PAGE_RULE]):
|
fileext = os.path.splitext(book_file)[-1].lower()
|
||||||
page_rules = [r for r in newsheet if r.type == r.PAGE_RULE]
|
debug_print("KoboTouch:_modify_epub: Modifying {0}".format(cssname))
|
||||||
if len(page_rules) > 0:
|
if self._modify_stylesheet(newsheet, fileext):
|
||||||
debug_print("KoboTouch:_modify_epub:Removing existing @page rules")
|
debug_print("KoboTouch:_modify_epub:CSS rules {0} -> {1} ({2})".format(oldrules, len(newsheet.cssRules), cssname))
|
||||||
for rule in page_rules:
|
container.dirty(cssname)
|
||||||
rule.style = ''
|
is_dirty = True
|
||||||
# remove any existing widow/orphan settings in epub css
|
|
||||||
# if css to be appended contains a widow/orphan rule or we there is no extra CSS file
|
|
||||||
if (len([r for r in self.extra_sheet if r.type == r.STYLE_RULE
|
|
||||||
and (r.style['widows'] or r.style['orphans'])]) > 0):
|
|
||||||
widow_orphan_rules = [r for r in newsheet if r.type == r.STYLE_RULE
|
|
||||||
and (r.style['widows'] or r.style['orphans'])]
|
|
||||||
if len(widow_orphan_rules) > 0:
|
|
||||||
debug_print("KoboTouch:_modify_epub:Removing existing widows/orphans attribs")
|
|
||||||
for rule in widow_orphan_rules:
|
|
||||||
rule.style.removeProperty('widows')
|
|
||||||
rule.style.removeProperty('orphans')
|
|
||||||
# append all rules from kobo extra css stylesheet
|
|
||||||
for addrule in [r for r in self.extra_sheet.cssRules]:
|
|
||||||
newsheet.insertRule(addrule, len(newsheet.cssRules))
|
|
||||||
debug_print("KoboTouch:_modify_epub:CSS rules {0} -> {1} ({2})".format(oldrules, len(newsheet.cssRules), cssname))
|
|
||||||
container.dirty(cssname)
|
|
||||||
|
|
||||||
if commit_container:
|
if commit_container:
|
||||||
debug_print("KoboTouch:_modify_epub: committing container.")
|
debug_print("KoboTouch:_modify_epub: committing container.")
|
||||||
os.unlink(file)
|
self.commit_container(container, is_dirty)
|
||||||
container.commit(file)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _modify_stylesheet(self, sheet, fileext, is_dirty=False):
|
||||||
|
from cssutils.css import CSSRule
|
||||||
|
|
||||||
|
#if fileext in (EPUB_EXT, KEPUB_EXT):
|
||||||
|
|
||||||
|
# if kobo extra css contains a @page rule
|
||||||
|
# remove any existing @page rules in epub css
|
||||||
|
if self.extra_css_options.get('has_atpage', False):
|
||||||
|
page_rules = self.get_extra_css_rules(sheet, CSSRule.PAGE_RULE)
|
||||||
|
if len(page_rules) > 0:
|
||||||
|
debug_print("KoboTouch:_modify_stylesheet: Removing existing @page rules")
|
||||||
|
for rule in page_rules:
|
||||||
|
rule.style = ''
|
||||||
|
is_dirty = True
|
||||||
|
|
||||||
|
# if kobo extra css contains any widow/orphan style rules
|
||||||
|
# remove any existing widow/orphan settings in epub css
|
||||||
|
if self.extra_css_options.get('has_widows_orphans', False):
|
||||||
|
widow_orphan_rules = self.get_extra_css_rules_widow_orphan(sheet)
|
||||||
|
if len(widow_orphan_rules) > 0:
|
||||||
|
debug_print("KoboTouch:_modify_stylesheet: Removing existing widows/orphans attribs")
|
||||||
|
for rule in widow_orphan_rules:
|
||||||
|
rule.style.removeProperty('widows')
|
||||||
|
rule.style.removeProperty('orphans')
|
||||||
|
is_dirty = True
|
||||||
|
|
||||||
|
# append all rules from kobo extra css
|
||||||
|
debug_print("KoboTouch:_modify_stylesheet: Append all kobo extra css rules")
|
||||||
|
for extra_rule in self.extra_sheet.cssRules:
|
||||||
|
sheet.insertRule(extra_rule)
|
||||||
|
is_dirty = True
|
||||||
|
|
||||||
|
return is_dirty
|
||||||
|
|
||||||
|
def create_container(self, book_file, metadata, container=None):
|
||||||
|
# create new container if not received, else pass through
|
||||||
|
if not container:
|
||||||
|
commit_container = True
|
||||||
|
try:
|
||||||
|
from calibre.ebooks.oeb.polish.container import get_container
|
||||||
|
debug_print("KoboTouch:create_container: try to create new container")
|
||||||
|
container = get_container(book_file)
|
||||||
|
container.css_preprocessor = DummyCSSPreProcessor()
|
||||||
|
except Exception as e:
|
||||||
|
debug_print("KoboTouch:create_container: exception from get_container {0} - {1}".format(metadata.author_sort, metadata.title))
|
||||||
|
debug_print("KoboTouch:create_container: exception is: {0}".format(e))
|
||||||
|
else:
|
||||||
|
commit_container = False
|
||||||
|
debug_print("KoboTouch:create_container: received container")
|
||||||
|
return container, commit_container
|
||||||
|
|
||||||
|
def commit_container(self, container, is_dirty=True):
|
||||||
|
# commit container if changes have been made
|
||||||
|
if is_dirty:
|
||||||
|
debug_print("KoboTouch:commit_container: commit container.")
|
||||||
|
container.commit()
|
||||||
|
|
||||||
|
# Clean-up-AYGO prevents build-up of TEMP exploded epub/kepub files
|
||||||
|
debug_print("KoboTouch:commit_container: removing container temp files.")
|
||||||
|
try:
|
||||||
|
shutil.rmtree(container.root)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def delete_via_sql(self, ContentID, ContentType):
|
def delete_via_sql(self, ContentID, ContentType):
|
||||||
imageId = super(KOBOTOUCH, self).delete_via_sql(ContentID, ContentType)
|
imageId = super(KOBOTOUCH, self).delete_via_sql(ContentID, ContentType)
|
||||||
|
|
||||||
@ -2148,7 +2187,6 @@ class KOBOTOUCH(KOBO):
|
|||||||
"Closed": 3,
|
"Closed": 3,
|
||||||
"Shortlist": 4,
|
"Shortlist": 4,
|
||||||
"Archived": 5,
|
"Archived": 5,
|
||||||
# "Preview":99, # Unsupported as we don't want to change it
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Define lists for the ReadStatus
|
# Define lists for the ReadStatus
|
||||||
@ -2163,33 +2201,16 @@ class KOBOTOUCH(KOBO):
|
|||||||
"Recommendation":4,
|
"Recommendation":4,
|
||||||
"Deleted":1,
|
"Deleted":1,
|
||||||
}
|
}
|
||||||
|
|
||||||
# specialshelveslist = {
|
|
||||||
# "Shortlist":1,
|
|
||||||
# "Wishlist":2,
|
|
||||||
# }
|
|
||||||
# debug_print('KoboTouch:update_device_database_collections - collections_attributes=', collections_attributes)
|
# debug_print('KoboTouch:update_device_database_collections - collections_attributes=', collections_attributes)
|
||||||
|
|
||||||
opts = self.settings()
|
create_bookshelves = self.create_bookshelves
|
||||||
if opts.extra_customization:
|
delete_empty_shelves = self.delete_empty_shelves
|
||||||
create_bookshelves = opts.extra_customization[self.OPT_CREATE_BOOKSHELVES] and self.supports_bookshelves()
|
update_series_details = self.update_series_details
|
||||||
delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves()
|
debugging_title = self.get_debugging_title()
|
||||||
update_series_details = opts.extra_customization[self.OPT_UPDATE_SERIES_DETAILS] and self.supports_series()
|
debug_print("KoboTouch:update_device_database_collections - set_debugging_title to '%s'" % debugging_title)
|
||||||
debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
|
booklists.set_debugging_title(debugging_title)
|
||||||
debug_print("KoboTouch:update_device_database_collections - set_debugging_title to '%s'" % debugging_title)
|
|
||||||
booklists.set_debugging_title(debugging_title)
|
|
||||||
else:
|
|
||||||
delete_empty_shelves = False
|
|
||||||
create_bookshelves = False
|
|
||||||
update_series_details = False
|
|
||||||
|
|
||||||
opts = self.settings()
|
bookshelf_attribute = len(collections_attributes) > 0
|
||||||
if opts.extra_customization:
|
|
||||||
create_bookshelves = opts.extra_customization[self.OPT_CREATE_BOOKSHELVES] and self.supports_bookshelves()
|
|
||||||
delete_empty_shelves = opts.extra_customization[self.OPT_DELETE_BOOKSHELVES] and self.supports_bookshelves()
|
|
||||||
else:
|
|
||||||
delete_empty_shelves = False
|
|
||||||
bookshelf_attribute = len(collections_attributes)
|
|
||||||
|
|
||||||
collections = booklists.get_collections(collections_attributes) if bookshelf_attribute else None
|
collections = booklists.get_collections(collections_attributes) if bookshelf_attribute else None
|
||||||
# debug_print('KoboTouch:update_device_database_collections - Collections:', collections)
|
# debug_print('KoboTouch:update_device_database_collections - Collections:', collections)
|
||||||
@ -2200,13 +2221,12 @@ class KOBOTOUCH(KOBO):
|
|||||||
# and the removal of the last book would not occur
|
# and the removal of the last book would not occur
|
||||||
|
|
||||||
import sqlite3 as sqlite
|
import sqlite3 as sqlite
|
||||||
with closing(sqlite.connect(self.normalize_path(self._main_prefix +
|
with closing(sqlite.connect(self.device_database_path())) as connection:
|
||||||
'.kobo/KoboReader.sqlite'))) as connection:
|
|
||||||
|
|
||||||
# return bytestrings if the content cannot the decoded as unicode
|
# return bytestrings if the content cannot the decoded as unicode
|
||||||
connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")
|
connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")
|
||||||
|
|
||||||
if collections:
|
if self.manage_collections and collections:
|
||||||
# debug_print("KoboTouch:update_device_database_collections - length collections=" + unicode(len(collections)))
|
# debug_print("KoboTouch:update_device_database_collections - length collections=" + unicode(len(collections)))
|
||||||
|
|
||||||
# Need to reset the collections outside the particular loops
|
# Need to reset the collections outside the particular loops
|
||||||
@ -2248,7 +2268,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path)
|
ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path)
|
||||||
book.contentID = self.contentid_from_path(book.path, ContentType)
|
book.contentID = self.contentid_from_path(book.path, ContentType)
|
||||||
|
|
||||||
if category in self.bookshelvelist and self.supports_bookshelves():
|
if category in self.bookshelvelist and self.supports_bookshelves:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' length book.device_collections=%d'%len(book.device_collections))
|
debug_print(' length book.device_collections=%d'%len(book.device_collections))
|
||||||
if category not in book.device_collections:
|
if category not in book.device_collections:
|
||||||
@ -2265,7 +2285,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' Have an older version shortlist - %s'%book.title)
|
debug_print(' Have an older version shortlist - %s'%book.title)
|
||||||
# Manage FavouritesIndex/Shortlist
|
# Manage FavouritesIndex/Shortlist
|
||||||
if not self.supports_bookshelves():
|
if not self.supports_bookshelves:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print(' and about to set it - %s'%book.title)
|
debug_print(' and about to set it - %s'%book.title)
|
||||||
self.set_favouritesindex(connection, book.contentID)
|
self.set_favouritesindex(connection, book.contentID)
|
||||||
@ -2293,7 +2313,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.reset_favouritesindex(connection, oncard)
|
self.reset_favouritesindex(connection, oncard)
|
||||||
|
|
||||||
# Set the series info and cleanup the bookshelves only if the firmware supports them and the user has set the options.
|
# Set the series info and cleanup the bookshelves only if the firmware supports them and the user has set the options.
|
||||||
if (self.supports_bookshelves() or self.supports_series()) and (bookshelf_attribute or update_series_details):
|
if (self.supports_bookshelves and self.manage_collections or self.supports_series()) and (bookshelf_attribute or update_series_details):
|
||||||
debug_print("KoboTouch:update_device_database_collections - managing bookshelves and series.")
|
debug_print("KoboTouch:update_device_database_collections - managing bookshelves and series.")
|
||||||
|
|
||||||
self.series_set = 0
|
self.series_set = 0
|
||||||
@ -2306,7 +2326,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print("KoboTouch:update_device_database_collections - book.title=%s" % book.title)
|
debug_print("KoboTouch:update_device_database_collections - book.title=%s" % book.title)
|
||||||
if update_series_details:
|
if update_series_details:
|
||||||
self.set_series(connection, book)
|
self.set_series(connection, book)
|
||||||
if bookshelf_attribute:
|
if self.manage_collections and bookshelf_attribute:
|
||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s" % book.title)
|
debug_print("KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s" % book.title)
|
||||||
self.remove_book_from_device_bookshelves(connection, book)
|
self.remove_book_from_device_bookshelves(connection, book)
|
||||||
@ -2341,8 +2361,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
debug_print("KoboTouch:upload_cover - path='%s' filename='%s' "%(path, filename))
|
debug_print("KoboTouch:upload_cover - path='%s' filename='%s' "%(path, filename))
|
||||||
debug_print(" filepath='%s' "%(filepath))
|
debug_print(" filepath='%s' "%(filepath))
|
||||||
|
|
||||||
opts = self.settings()
|
if not self.upload_covers:
|
||||||
if not self.copying_covers():
|
|
||||||
# Building thumbnails disabled
|
# Building thumbnails disabled
|
||||||
# debug_print('KoboTouch: not uploading cover')
|
# debug_print('KoboTouch: not uploading cover')
|
||||||
return
|
return
|
||||||
@ -2351,14 +2370,9 @@ class KOBOTOUCH(KOBO):
|
|||||||
if self._card_a_prefix and os.path.abspath(path).startswith(os.path.abspath(self._card_a_prefix)) and not self.supports_covers_on_sdcard():
|
if self._card_a_prefix and os.path.abspath(path).startswith(os.path.abspath(self._card_a_prefix)) and not self.supports_covers_on_sdcard():
|
||||||
return
|
return
|
||||||
|
|
||||||
if not opts.extra_customization[self.OPT_UPLOAD_GRAYSCALE_COVERS]:
|
|
||||||
uploadgrayscale = False
|
|
||||||
else:
|
|
||||||
uploadgrayscale = True
|
|
||||||
|
|
||||||
# debug_print('KoboTouch: uploading cover')
|
# debug_print('KoboTouch: uploading cover')
|
||||||
try:
|
try:
|
||||||
self._upload_cover(path, filename, metadata, filepath, uploadgrayscale, self.keep_cover_aspect())
|
self._upload_cover(path, filename, metadata, filepath, self.upload_grayscale, self.keep_cover_aspect)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, str(e)))
|
debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, str(e)))
|
||||||
|
|
||||||
@ -2589,7 +2603,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
# debug_print('KoboTouch:get_bookshelflist')
|
# debug_print('KoboTouch:get_bookshelflist')
|
||||||
bookshelves = []
|
bookshelves = []
|
||||||
|
|
||||||
if not self.supports_bookshelves():
|
if not self.supports_bookshelves:
|
||||||
return bookshelves
|
return bookshelves
|
||||||
|
|
||||||
query = 'SELECT Name FROM Shelf WHERE _IsDeleted = "false"'
|
query = 'SELECT Name FROM Shelf WHERE _IsDeleted = "false"'
|
||||||
@ -2691,7 +2705,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
|
|
||||||
def remove_from_bookshelves(self, connection, oncard, ContentID=None, bookshelves=None):
|
def remove_from_bookshelves(self, connection, oncard, ContentID=None, bookshelves=None):
|
||||||
debug_print('KoboTouch:remove_from_bookshelf ContentID=', ContentID)
|
debug_print('KoboTouch:remove_from_bookshelf ContentID=', ContentID)
|
||||||
if not self.supports_bookshelves():
|
if not self.supports_bookshelves:
|
||||||
return
|
return
|
||||||
query = 'DELETE FROM ShelfContent'
|
query = 'DELETE FROM ShelfContent'
|
||||||
|
|
||||||
@ -2762,10 +2776,72 @@ class KOBOTOUCH(KOBO):
|
|||||||
if show_debug:
|
if show_debug:
|
||||||
debug_print("KoboTouch:set_series - end")
|
debug_print("KoboTouch:set_series - end")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def config_widget(self):
|
||||||
|
# TODO: Cleanup the following
|
||||||
|
self.current_friendly_name = self.gui_name
|
||||||
|
|
||||||
|
from calibre.devices.kobo.kobotouch_config import KOBOTOUCHConfig
|
||||||
|
return KOBOTOUCHConfig(self.settings(), self.FORMATS,
|
||||||
|
self.SUPPORTS_SUB_DIRS, self.MUST_READ_METADATA,
|
||||||
|
self.SUPPORTS_USE_AUTHOR_SORT, self.EXTRA_CUSTOMIZATION_MESSAGE,
|
||||||
|
self, extra_customization_choices=self.EXTRA_CUSTOMIZATION_CHOICES
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_pref(cls, key):
|
||||||
|
''' Get the setting named key. First looks for a device specific setting.
|
||||||
|
If that is not found looks for a device default and if that is not
|
||||||
|
found uses the global default.'''
|
||||||
|
# debug_print("KoboTouch::get_prefs - key=", key, "cls=", cls)
|
||||||
|
opts = cls.settings()
|
||||||
|
try:
|
||||||
|
return getattr(opts, key)
|
||||||
|
except:
|
||||||
|
debug_print("KoboTouch::get_prefs - probably an extra_customization:", key)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_settings(cls, config_widget):
|
||||||
|
config_widget.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def save_template(cls):
|
||||||
|
return cls.settings().save_template
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _config(cls):
|
||||||
|
c = super(KOBOTOUCH, cls)._config()
|
||||||
|
|
||||||
|
c.add_opt('manage_collections', default=True)
|
||||||
|
c.add_opt('collections_columns', default='')
|
||||||
|
c.add_opt('create_collections', default=False)
|
||||||
|
c.add_opt('delete_empty_collections', default=False)
|
||||||
|
|
||||||
|
c.add_opt('upload_covers', default=False)
|
||||||
|
c.add_opt('keep_cover_aspect', default=False)
|
||||||
|
c.add_opt('upload_grayscale', default=False)
|
||||||
|
|
||||||
|
c.add_opt('show_archived_books', default=False)
|
||||||
|
c.add_opt('show_previews', default=False)
|
||||||
|
c.add_opt('show_recommendations', default=False)
|
||||||
|
|
||||||
|
c.add_opt('update_series', default=True)
|
||||||
|
c.add_opt('update_device_metadata', default=True)
|
||||||
|
|
||||||
|
c.add_opt('modify_css', default=False)
|
||||||
|
|
||||||
|
c.add_opt('support_newer_firmware', default=False)
|
||||||
|
c.add_opt('debugging_title', default='')
|
||||||
|
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def settings(cls):
|
def settings(cls):
|
||||||
opts = cls._config().parse()
|
opts = cls._config().parse()
|
||||||
if isinstance(cls.EXTRA_CUSTOMIZATION_DEFAULT, list):
|
if isinstance(cls.EXTRA_CUSTOMIZATION_DEFAULT, list) and len(cls.EXTRA_CUSTOMIZATION_DEFAULT) > 0:
|
||||||
if opts.extra_customization is None:
|
if opts.extra_customization is None:
|
||||||
opts.extra_customization = []
|
opts.extra_customization = []
|
||||||
if not isinstance(opts.extra_customization, list):
|
if not isinstance(opts.extra_customization, list):
|
||||||
@ -2782,6 +2858,10 @@ class KOBOTOUCH(KOBO):
|
|||||||
else:
|
else:
|
||||||
extra_customization.append(opts.extra_customization[i - extra_options_offset])
|
extra_customization.append(opts.extra_customization[i - extra_options_offset])
|
||||||
opts.extra_customization = extra_customization
|
opts.extra_customization = extra_customization
|
||||||
|
if opts.extra_customization:
|
||||||
|
opts = cls.migrate_old_settings(opts)
|
||||||
|
|
||||||
|
cls.opts = opts
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def isAura(self):
|
def isAura(self):
|
||||||
@ -2827,24 +2907,73 @@ class KOBOTOUCH(KOBO):
|
|||||||
self.__class__.gui_name = device_name
|
self.__class__.gui_name = device_name
|
||||||
return device_name
|
return device_name
|
||||||
|
|
||||||
def copying_covers(self):
|
@property
|
||||||
opts = self.settings()
|
def manage_collections(self):
|
||||||
return opts.extra_customization[self.OPT_UPLOAD_COVERS] or opts.extra_customization[self.OPT_KEEP_COVER_ASPECT_RATIO]
|
return self.get_pref('manage_collections')
|
||||||
|
@property
|
||||||
|
def create_collections(self):
|
||||||
|
return self.get_pref('create_collections')
|
||||||
|
@property
|
||||||
|
def collections_columns(self):
|
||||||
|
return self.get_pref('collections_columns')
|
||||||
|
@property
|
||||||
|
def delete_empty_collections(self):
|
||||||
|
return self.get_pref('delete_empty_collections')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def upload_covers(self):
|
||||||
|
return self.get_pref('upload_covers')
|
||||||
|
@property
|
||||||
def keep_cover_aspect(self):
|
def keep_cover_aspect(self):
|
||||||
opts = self.settings()
|
return self.upload_covers and self.get_pref('keep_cover_aspect')
|
||||||
return opts.extra_customization[self.OPT_KEEP_COVER_ASPECT_RATIO]
|
@property
|
||||||
|
def upload_grayscale(self):
|
||||||
|
return self.upload_covers and self.get_pref('upload_grayscale')
|
||||||
|
|
||||||
def modifying_epub(self):
|
def modifying_epub(self):
|
||||||
return self.modifying_css()
|
return self.modifying_css()
|
||||||
|
|
||||||
def modifying_css(self):
|
def modifying_css(self):
|
||||||
opts = self.settings()
|
return self.get_pref('modify_css')
|
||||||
return opts.extra_customization[self.OPT_MODIFY_CSS]
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def create_bookshelves(self):
|
||||||
|
return self.get_pref('create_collections') and self.supports_bookshelves
|
||||||
|
@property
|
||||||
|
def delete_empty_shelves(self):
|
||||||
|
return self.get_pref('delete_empty_collections') and self.supports_bookshelves
|
||||||
|
@property
|
||||||
|
def update_device_metadata(self):
|
||||||
|
return self.get_pref('update_device_metadata')
|
||||||
|
@property
|
||||||
|
def update_series_details(self):
|
||||||
|
return self.update_device_metadata and self.get_pref('update_series') and self.supports_series()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_debugging_title(cls):
|
||||||
|
debugging_title = cls.get_pref('debugging_title')
|
||||||
|
if not debugging_title: # Make sure the value is set to prevent rereading the settings.
|
||||||
|
debugging_title = ''
|
||||||
|
return debugging_title
|
||||||
|
|
||||||
|
@property
|
||||||
def supports_bookshelves(self):
|
def supports_bookshelves(self):
|
||||||
return self.dbversion >= self.min_supported_dbversion
|
return self.dbversion >= self.min_supported_dbversion
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_archived_books(self):
|
||||||
|
return self.get_pref('show_archived_books')
|
||||||
|
@property
|
||||||
|
def show_previews(self):
|
||||||
|
return self.get_pref('show_previews')
|
||||||
|
@property
|
||||||
|
def show_recommendations(self):
|
||||||
|
return self.get_pref('show_recommendations')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def read_metadata(self):
|
||||||
|
return self.get_pref('read_metadata')
|
||||||
|
|
||||||
def supports_series(self):
|
def supports_series(self):
|
||||||
return self.dbversion >= self.min_dbversion_series
|
return self.dbversion >= self.min_dbversion_series
|
||||||
|
|
||||||
@ -2869,8 +2998,7 @@ class KOBOTOUCH(KOBO):
|
|||||||
# debug_print("KoboTouch:modify_database_check - self.fwversion > self.max_supported_fwversion=", self.fwversion > self.max_supported_fwversion)
|
# debug_print("KoboTouch:modify_database_check - self.fwversion > self.max_supported_fwversion=", self.fwversion > self.max_supported_fwversion)
|
||||||
if self.dbversion > self.supported_dbversion or self.fwversion > self.max_supported_fwversion:
|
if self.dbversion > self.supported_dbversion or self.fwversion > self.max_supported_fwversion:
|
||||||
# Unsupported database
|
# Unsupported database
|
||||||
opts = self.settings()
|
if not self.get_pref('support_newer_firmware'):
|
||||||
if not opts.extra_customization[self.OPT_SUPPORT_NEWER_FIRMWARE]:
|
|
||||||
debug_print('The database has been upgraded past supported version')
|
debug_print('The database has been upgraded past supported version')
|
||||||
self.report_progress(1.0, _('Removing books from device...'))
|
self.report_progress(1.0, _('Removing books from device...'))
|
||||||
from calibre.devices.errors import UserFeedback
|
from calibre.devices.errors import UserFeedback
|
||||||
@ -2901,21 +3029,77 @@ class KOBOTOUCH(KOBO):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_debugging_title(cls, title):
|
def migrate_old_settings(cls, settings):
|
||||||
|
debug_print("KoboTouch::migrate_old_settings - start")
|
||||||
|
|
||||||
|
count_options = 0
|
||||||
|
OPT_COLLECTIONS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_CREATE_BOOKSHELVES = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_DELETE_BOOKSHELVES = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_UPLOAD_COVERS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_UPLOAD_GRAYSCALE_COVERS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_KEEP_COVER_ASPECT_RATIO = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_SHOW_ARCHIVED_BOOK_RECORDS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_SHOW_PREVIEWS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_SHOW_RECOMMENDATIONS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_UPDATE_SERIES_DETAILS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_MODIFY_CSS = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_SUPPORT_NEWER_FIRMWARE = count_options
|
||||||
|
count_options += 1
|
||||||
|
OPT_DEBUGGING_TITLE = count_options
|
||||||
|
|
||||||
|
if len(settings.extra_customization) >= count_options:
|
||||||
|
debug_print("KoboTouch::migrate_old_settings - settings need to be migrated")
|
||||||
|
settings.manage_collections = True
|
||||||
|
settings.collections_columns = settings.extra_customization[OPT_COLLECTIONS]
|
||||||
|
debug_print("KoboTouch::migrate_old_settings - settings.collections_columns=", settings.collections_columns)
|
||||||
|
settings.create_collections = settings.extra_customization[OPT_CREATE_BOOKSHELVES]
|
||||||
|
settings.delete_empty_collections = settings.extra_customization[OPT_DELETE_BOOKSHELVES]
|
||||||
|
|
||||||
|
settings.upload_covers = settings.extra_customization[OPT_UPLOAD_COVERS]
|
||||||
|
settings.keep_cover_aspect = settings.extra_customization[OPT_KEEP_COVER_ASPECT_RATIO]
|
||||||
|
settings.upload_grayscale = settings.extra_customization[OPT_UPLOAD_GRAYSCALE_COVERS]
|
||||||
|
|
||||||
|
settings.show_archived_books = settings.extra_customization[OPT_SHOW_ARCHIVED_BOOK_RECORDS]
|
||||||
|
settings.show_previews = settings.extra_customization[OPT_SHOW_PREVIEWS]
|
||||||
|
settings.show_recommendations = settings.extra_customization[OPT_SHOW_RECOMMENDATIONS]
|
||||||
|
|
||||||
|
settings.update_series = settings.extra_customization[OPT_UPDATE_SERIES_DETAILS]
|
||||||
|
settings.update_metadata = settings.update_series
|
||||||
|
|
||||||
|
settings.modify_css = settings.extra_customization[OPT_MODIFY_CSS]
|
||||||
|
|
||||||
|
settings.support_newer_firmware = settings.extra_customization[OPT_SUPPORT_NEWER_FIRMWARE]
|
||||||
|
settings.debugging_title = settings.extra_customization[OPT_DEBUGGING_TITLE]
|
||||||
|
settings.extra_customization = settings.extra_customization[count_options + 1:]
|
||||||
|
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
|
def is_debugging_title(self, title):
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
return False
|
return False
|
||||||
# debug_print("KoboTouch:is_debugging - title=", title)
|
# debug_print("KoboTouch:is_debugging - title=", title)
|
||||||
is_debugging = False
|
|
||||||
opts = cls.settings()
|
|
||||||
|
|
||||||
if opts.extra_customization:
|
if not self.debugging_title and not self.debugging_title == '':
|
||||||
debugging_title = opts.extra_customization[cls.OPT_DEBUGGING_TITLE]
|
self.debugging_title = self.get_debugging_title()
|
||||||
is_debugging = len(debugging_title) > 0 and title.lower().find(debugging_title.lower()) >= 0 or len(title) == 0
|
is_debugging = len(self.debugging_title) > 0 and title.lower().find(self.debugging_title.lower()) >= 0 or len(title) == 0
|
||||||
|
|
||||||
return is_debugging
|
return is_debugging
|
||||||
|
|
||||||
def dump_bookshelves(self, connection):
|
def dump_bookshelves(self, connection):
|
||||||
if not (DEBUG and self.supports_bookshelves() and False):
|
if not (DEBUG and self.supports_bookshelves and False):
|
||||||
return
|
return
|
||||||
|
|
||||||
debug_print('KoboTouch:dump_bookshelves - start')
|
debug_print('KoboTouch:dump_bookshelves - start')
|
||||||
@ -2952,3 +3136,29 @@ class KOBOTOUCH(KOBO):
|
|||||||
cursor.close()
|
cursor.close()
|
||||||
debug_print('KoboTouch:dump_bookshelves - end')
|
debug_print('KoboTouch:dump_bookshelves - end')
|
||||||
|
|
||||||
|
def __str__(self, *args, **kwargs):
|
||||||
|
options = ', '.join(['%s: %s' % (x.name, self.get_pref(x.name)) for x in self._config().preferences])
|
||||||
|
return u"Driver:%s, Options - %s" % (self.name, options)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
dev = KOBOTOUCH(None)
|
||||||
|
dev.startup()
|
||||||
|
try:
|
||||||
|
dev.initialize()
|
||||||
|
from calibre.devices.scanner import DeviceScanner
|
||||||
|
scanner = DeviceScanner()
|
||||||
|
scanner.scan()
|
||||||
|
devs = scanner.devices
|
||||||
|
# debug_print("unit test: devs.__class__=", devs.__class__)
|
||||||
|
# debug_print("unit test: devs.__class__=", devs.__class__.__name__)
|
||||||
|
debug_print("unit test: devs=", devs)
|
||||||
|
debug_print("unit test: dev=", dev)
|
||||||
|
# cd = dev.detect_managed_devices(devs)
|
||||||
|
# if cd is None:
|
||||||
|
# raise ValueError('Failed to detect KOBOTOUCH device')
|
||||||
|
dev.set_progress_reporter(prints)
|
||||||
|
# dev.open(cd, None)
|
||||||
|
# dev.filesystem_cache.dump()
|
||||||
|
print ('Prefix for main memory:', dev.dbversion)
|
||||||
|
finally:
|
||||||
|
dev.shutdown()
|
||||||
|
450
src/calibre/devices/kobo/kobotouch_config.py
Normal file
450
src/calibre/devices/kobo/kobotouch_config.py
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from PyQt5.Qt import (QLabel, QGridLayout, QLineEdit, QVBoxLayout,
|
||||||
|
QDialog, QDialogButtonBox, QCheckBox)
|
||||||
|
|
||||||
|
from calibre.gui2.device_drivers.tabbed_device_config import TabbedDeviceConfig, DeviceConfigTab, DeviceOptionsGroupBox
|
||||||
|
from calibre.devices.usbms.driver import debug_print
|
||||||
|
|
||||||
|
def wrap_msg(msg):
|
||||||
|
return textwrap.fill(msg.strip(), 100)
|
||||||
|
|
||||||
|
def setToolTipFor(widget, tt):
|
||||||
|
widget.setToolTip(wrap_msg(tt))
|
||||||
|
|
||||||
|
def create_checkbox(title, tt, state):
|
||||||
|
cb = QCheckBox(title)
|
||||||
|
cb.setToolTip(wrap_msg(tt))
|
||||||
|
cb.setChecked(bool(state))
|
||||||
|
return cb
|
||||||
|
|
||||||
|
|
||||||
|
class KOBOTOUCHConfig(TabbedDeviceConfig):
|
||||||
|
|
||||||
|
def __init__(self, device_settings, all_formats, supports_subdirs,
|
||||||
|
must_read_metadata, supports_use_author_sort,
|
||||||
|
extra_customization_message, device, extra_customization_choices=None, parent=None):
|
||||||
|
|
||||||
|
super(KOBOTOUCHConfig, self).__init__(device_settings, all_formats, supports_subdirs,
|
||||||
|
must_read_metadata, supports_use_author_sort,
|
||||||
|
extra_customization_message, device, extra_customization_choices, parent)
|
||||||
|
|
||||||
|
self.device_settings = device_settings
|
||||||
|
self.all_formats = all_formats
|
||||||
|
self.supports_subdirs = supports_subdirs
|
||||||
|
self.must_read_metadata = must_read_metadata
|
||||||
|
self.supports_use_author_sort = supports_use_author_sort
|
||||||
|
self.extra_customization_message = extra_customization_message
|
||||||
|
self.extra_customization_choices = extra_customization_choices
|
||||||
|
|
||||||
|
self.tab1 = Tab1Config(self, self.device)
|
||||||
|
self.tab2 = Tab2Config(self, self.device)
|
||||||
|
|
||||||
|
self.addDeviceTab(self.tab1, _("Collections, Covers && Uploads"))
|
||||||
|
self.addDeviceTab(self.tab2, _('Metadata, On Device && Advanced'))
|
||||||
|
|
||||||
|
def get_pref(self, key):
|
||||||
|
return self.device.get_pref(key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device(self):
|
||||||
|
return self._device()
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
if hasattr(self, 'formats'):
|
||||||
|
if not self.formats.validate():
|
||||||
|
return False
|
||||||
|
if not self.template.validate():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def book_uploads_options(self):
|
||||||
|
return self.tab1.book_uploads_options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def collections_options(self):
|
||||||
|
return self.tab1.collections_options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cover_options(self):
|
||||||
|
return self.tab1.covers_options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_list_options(self):
|
||||||
|
return self.tab2.device_list_options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def advanced_options(self):
|
||||||
|
return self.tab2.advanced_options
|
||||||
|
|
||||||
|
@property
|
||||||
|
def metadata_options(self):
|
||||||
|
return self.tab2.metadata_options
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
debug_print("KOBOTOUCHConfig::commit: start")
|
||||||
|
p = super(KOBOTOUCHConfig, self).commit()
|
||||||
|
|
||||||
|
p['manage_collections'] = self.manage_collections
|
||||||
|
p['create_collections'] = self.create_collections
|
||||||
|
p['collections_columns'] = self.collections_columns
|
||||||
|
p['delete_empty_collections'] = self.delete_empty_collections
|
||||||
|
|
||||||
|
p['upload_covers'] = self.upload_covers
|
||||||
|
p['keep_cover_aspect'] = self.keep_cover_aspect
|
||||||
|
p['upload_grayscale'] = self.upload_grayscale
|
||||||
|
|
||||||
|
p['show_recommendations'] = self.show_recommendations
|
||||||
|
p['show_previews'] = self.show_previews
|
||||||
|
p['show_archived_books'] = self.show_archived_books
|
||||||
|
|
||||||
|
p['update_series'] = self.update_series
|
||||||
|
p['modify_css'] = self.modify_css
|
||||||
|
|
||||||
|
p['support_newer_firmware'] = self.support_newer_firmware
|
||||||
|
p['debugging_title'] = self.debugging_title
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
class Tab1Config(DeviceConfigTab): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(Tab1Config, self).__init__(parent)
|
||||||
|
|
||||||
|
self.l = QVBoxLayout(self)
|
||||||
|
self.setLayout(self.l)
|
||||||
|
|
||||||
|
self.collections_options = CollectionsGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.collections_options)
|
||||||
|
self.addDeviceWidget(self.collections_options)
|
||||||
|
|
||||||
|
self.covers_options = CoversGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.covers_options)
|
||||||
|
self.addDeviceWidget(self.covers_options)
|
||||||
|
|
||||||
|
self.book_uploads_options = BookUploadsGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.book_uploads_options)
|
||||||
|
self.addDeviceWidget(self.book_uploads_options)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class Tab2Config(DeviceConfigTab): # {{{
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(Tab2Config, self).__init__(parent)
|
||||||
|
|
||||||
|
self.l = QVBoxLayout(self)
|
||||||
|
self.setLayout(self.l)
|
||||||
|
|
||||||
|
self.metadata_options = MetadataGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.metadata_options)
|
||||||
|
self.addDeviceWidget(self.metadata_options)
|
||||||
|
|
||||||
|
self.device_list_options = DeviceListGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.device_list_options)
|
||||||
|
self.addDeviceWidget(self.device_list_options)
|
||||||
|
|
||||||
|
self.advanced_options = AdvancedGroupBox(self, device)
|
||||||
|
self.l.addWidget(self.advanced_options)
|
||||||
|
self.addDeviceWidget(self.advanced_options)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
class BookUploadsGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(BookUploadsGroupBox, self).__init__(parent, device)
|
||||||
|
self.setTitle(_("Book Uploading"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.modify_css_checkbox = create_checkbox(
|
||||||
|
_("Modify CSS"),
|
||||||
|
_('This allows addition of user CSS rules and removal of some CSS. '
|
||||||
|
'When sending a book, the driver adds the contents of {0} to all stylesheets in the ePub. '
|
||||||
|
'This file is searched for in the root directory of the main memory of the device. '
|
||||||
|
'As well as this, if the file contains settings for the "orphans" or "widows", '
|
||||||
|
'these are removed for all styles in the original stylesheet.').format(device.KOBO_EXTRA_CSSFILE),
|
||||||
|
device.get_pref('modify_css')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.options_layout.addWidget(self.modify_css_checkbox, 0, 0, 1, 2)
|
||||||
|
self.options_layout.setRowStretch(1, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def modify_css(self):
|
||||||
|
return self.modify_css_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionsGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(CollectionsGroupBox, self).__init__(parent, device)
|
||||||
|
self.setTitle(_("Collections"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.setCheckable(True)
|
||||||
|
self.setChecked(device.get_pref('manage_collections'))
|
||||||
|
self.setToolTip(wrap_msg(_('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.')))
|
||||||
|
|
||||||
|
self.collections_columns_label = QLabel(_('Collections Columns'))
|
||||||
|
self.collections_columns_edit = QLineEdit(self)
|
||||||
|
self.collections_columns_edit.setToolTip(_('The Kobo from firmware V2.0.0 supports bookshelves.'
|
||||||
|
' These are created on the Kobo. ' +
|
||||||
|
'Specify a tags type column for automatic management.'))
|
||||||
|
self.collections_columns_edit.setText(device.get_pref('collections_columns'))
|
||||||
|
|
||||||
|
self.create_collections_checkbox = create_checkbox(
|
||||||
|
_("Create Collections"),
|
||||||
|
_('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.'),
|
||||||
|
device.get_pref('create_collections')
|
||||||
|
)
|
||||||
|
self.delete_empty_collections_checkbox = create_checkbox(
|
||||||
|
_('Delete Empty Bookshelves'),
|
||||||
|
_('Delete any empty bookshelves from the Kobo when syncing is finished. This is only for firmware V2.0.0 or later.'),
|
||||||
|
device.get_pref('delete_empty_collections')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.options_layout.addWidget(self.collections_columns_label, 1, 0, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.collections_columns_edit, 1, 1, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.create_collections_checkbox, 2, 0, 1, 2)
|
||||||
|
self.options_layout.addWidget(self.delete_empty_collections_checkbox, 3, 0, 1, 2)
|
||||||
|
self.options_layout.setRowStretch(4, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manage_collections(self):
|
||||||
|
return self.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def collections_columns(self):
|
||||||
|
return self.collections_columns_edit.text().strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def create_collections(self):
|
||||||
|
return self.create_collections_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def delete_empty_collections(self):
|
||||||
|
return self.delete_empty_collections_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
class CoversGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(CoversGroupBox, self).__init__(parent, device)
|
||||||
|
self.setTitle(_("Upload covers"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.setCheckable(True)
|
||||||
|
self.setChecked(device.get_pref('upload_covers'))
|
||||||
|
self.setToolTip(wrap_msg(_('Upload cover images from the calibre library when sending books to the device.')))
|
||||||
|
|
||||||
|
self.upload_grayscale_checkbox = create_checkbox(
|
||||||
|
_('Upload Black and White Covers'),
|
||||||
|
_('Convert covers to Black and White when uploading'),
|
||||||
|
device.get_pref('upload_grayscale')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.keep_cover_aspect_checkbox = create_checkbox(
|
||||||
|
_('Keep cover aspect ratio'),
|
||||||
|
_('When uploading covers, do not change the aspect ratio when resizing for the device.'
|
||||||
|
' This is for firmware versions 2.3.1 and later.'),
|
||||||
|
device.get_pref('keep_cover_aspect'))
|
||||||
|
|
||||||
|
self.options_layout.addWidget(self.keep_cover_aspect_checkbox, 0, 0, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.upload_grayscale_checkbox, 1, 0, 1, 1)
|
||||||
|
self.options_layout.setRowStretch(2, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def upload_covers(self):
|
||||||
|
return self.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def upload_grayscale(self):
|
||||||
|
return self.upload_grayscale_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def keep_cover_aspect(self):
|
||||||
|
return self.keep_cover_aspect_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceListGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(DeviceListGroupBox, self).__init__(parent, device)
|
||||||
|
self.setTitle(_("Show as on device"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.show_recommendations_checkbox = create_checkbox(
|
||||||
|
_("Show Recommendations"),
|
||||||
|
_('Kobo shows recommendations on the device. In some cases these have '
|
||||||
|
'files but in other cases they are just pointers to the web site to buy. '
|
||||||
|
'Enable if you wish to see/delete them.'),
|
||||||
|
device.get_pref('show_recommendations')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.show_archived_books_checkbox = create_checkbox(
|
||||||
|
_("Show archived books"),
|
||||||
|
_('Archived books are listed on the device but need to be downloaded to read.'
|
||||||
|
' Use this option to show these books and match them with books in the calibre library.'),
|
||||||
|
device.get_pref('show_archived_books')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.show_previews_checkbox = create_checkbox(
|
||||||
|
_('Show Previews'),
|
||||||
|
_('Kobo previews are included on the Touch and some other versions'
|
||||||
|
' by default they are no longer displayed as there is no good reason to '
|
||||||
|
'see them. Enable if you wish to see/delete them.'),
|
||||||
|
device.get_pref('show_previews')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.options_layout.addWidget(self.show_recommendations_checkbox, 0, 0, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.show_archived_books_checkbox, 1, 0, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.show_previews_checkbox, 2, 0, 1, 1)
|
||||||
|
self.options_layout.setRowStretch(3, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_recommendations(self):
|
||||||
|
return self.show_recommendations_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_archived_books(self):
|
||||||
|
return self.show_archived_books_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_previews(self):
|
||||||
|
return self.show_previews_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
class AdvancedGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(AdvancedGroupBox, self).__init__(parent, device, _("Advanced Options"))
|
||||||
|
# self.setTitle(_("Advanced Options"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.support_newer_firmware_checkbox = create_checkbox(
|
||||||
|
_("Attempt to support newer firmware"),
|
||||||
|
_('Kobo routinely updates the firmware and the '
|
||||||
|
'database version. With this option Calibre will attempt '
|
||||||
|
'to perform full read-write functionality - Here be Dragons!! '
|
||||||
|
'Enable only if you are comfortable with restoring your kobo '
|
||||||
|
'to factory defaults and testing software. '
|
||||||
|
'This driver supports firmware V2.x.x and DBVersion up to ') + unicode(
|
||||||
|
device.supported_dbversion), device.get_pref('support_newer_firmware')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.debugging_title_checkbox = create_checkbox(
|
||||||
|
_("Title to test when debugging"),
|
||||||
|
_('Part of title of a book that can be used when doing some tests for debugging. '
|
||||||
|
'The test is to see if the string is contained in the title of a book. '
|
||||||
|
'The better the match, the less extraneous output.'),
|
||||||
|
device.get_pref('debugging_title')
|
||||||
|
)
|
||||||
|
self.debugging_title_label = QLabel(_('Title to test when debugging'))
|
||||||
|
self.debugging_title_edit = QLineEdit(self)
|
||||||
|
self.debugging_title_edit.setToolTip(_('Part of title of a book that can be used when doing some tests for debugging. '
|
||||||
|
'The test is to see if the string is contained in the title of a book. '
|
||||||
|
'The better the match, the less extraneous output.'))
|
||||||
|
self.debugging_title_edit.setText(device.get_pref('debugging_title'))
|
||||||
|
self.debugging_title_label.setBuddy(self.debugging_title_edit)
|
||||||
|
|
||||||
|
self.options_layout.addWidget(self.support_newer_firmware_checkbox, 0, 0, 1, 2)
|
||||||
|
self.options_layout.addWidget(self.debugging_title_label, 1, 0, 1, 1)
|
||||||
|
self.options_layout.addWidget(self.debugging_title_edit, 1, 1, 1, 1)
|
||||||
|
self.options_layout.setRowStretch(2, 2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def support_newer_firmware(self):
|
||||||
|
return self.support_newer_firmware_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debugging_title(self):
|
||||||
|
return self.debugging_title_edit.text().strip()
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataGroupBox(DeviceOptionsGroupBox):
|
||||||
|
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super(MetadataGroupBox, self).__init__(parent, device)
|
||||||
|
self.setTitle(_("Update metadata on the device"))
|
||||||
|
|
||||||
|
self.options_layout = QGridLayout()
|
||||||
|
self.options_layout.setObjectName("options_layout")
|
||||||
|
self.setLayout(self.options_layout)
|
||||||
|
|
||||||
|
self.setCheckable(True)
|
||||||
|
self.setChecked(device.get_pref('update_device_metadata'))
|
||||||
|
self.setToolTip(wrap_msg(_('Update the metadata on the device when it is connected. '
|
||||||
|
'Be careful when doing this as it will take time and could make the initial connection take a long time.')))
|
||||||
|
|
||||||
|
self.update_series_checkbox = create_checkbox(
|
||||||
|
_("Set Series information"),
|
||||||
|
_('The book lists on the Kobo devices can display series information. '
|
||||||
|
'This is not read by the device from the sideloaded books. '
|
||||||
|
'Series information can only be added to the device after the book has been processed by the device. '
|
||||||
|
'Enable if you wish to set series information.'),
|
||||||
|
device.get_pref('update_series')
|
||||||
|
)
|
||||||
|
self.options_layout.addWidget(self.update_series_checkbox, 0, 0, 1, 1)
|
||||||
|
self.options_layout.setRowStretch(1, 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update_series(self):
|
||||||
|
return self.update_series_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update_device_metadata(self):
|
||||||
|
return self.isChecked()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
from calibre.devices.kobo.driver import KOBOTOUCH
|
||||||
|
from calibre.devices.scanner import DeviceScanner
|
||||||
|
s = DeviceScanner()
|
||||||
|
s.scan()
|
||||||
|
app = Application([])
|
||||||
|
debug_print("KOBOTOUCH:", KOBOTOUCH)
|
||||||
|
dev = KOBOTOUCH(None)
|
||||||
|
# dev.startup()
|
||||||
|
# cd = dev.detect_managed_devices(s.devices)
|
||||||
|
# dev.open(cd, 'test')
|
||||||
|
cw = dev.config_widget()
|
||||||
|
d = QDialog()
|
||||||
|
d.l = QVBoxLayout()
|
||||||
|
d.setLayout(d.l)
|
||||||
|
d.l.addWidget(cw)
|
||||||
|
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
d.l.addWidget(bb)
|
||||||
|
bb.accepted.connect(d.accept)
|
||||||
|
bb.rejected.connect(d.reject)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
cw.commit()
|
||||||
|
dev.shutdown()
|
||||||
|
|
||||||
|
|
390
src/calibre/gui2/device_drivers/tabbed_device_config.py
Normal file
390
src/calibre/gui2/device_drivers/tabbed_device_config.py
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||||
|
from __future__ import (unicode_literals, # division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import weakref, textwrap
|
||||||
|
|
||||||
|
from PyQt5.Qt import (
|
||||||
|
QWidget, QLabel, QTabWidget, QGridLayout, QLineEdit, QVBoxLayout,
|
||||||
|
QGroupBox, QComboBox, QSizePolicy, QDialog, QDialogButtonBox, QCheckBox,
|
||||||
|
QSpacerItem)
|
||||||
|
|
||||||
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
from calibre.gui2.device_drivers.mtp_config import (FormatsConfig, TemplateConfig)
|
||||||
|
from calibre.devices.usbms.driver import debug_print
|
||||||
|
|
||||||
|
def wrap_msg(msg):
|
||||||
|
return textwrap.fill(msg.strip(), 100)
|
||||||
|
|
||||||
|
def setToolTipFor(widget, tt):
|
||||||
|
widget.setToolTip(wrap_msg(tt))
|
||||||
|
|
||||||
|
def create_checkbox(title, tt, state):
|
||||||
|
cb = QCheckBox(title)
|
||||||
|
cb.setToolTip(wrap_msg(tt))
|
||||||
|
cb.setChecked(bool(state))
|
||||||
|
return cb
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedDeviceConfig(QTabWidget):
|
||||||
|
"""
|
||||||
|
This is a generic Tabbed Device config widget. It designed for devices with more
|
||||||
|
complex configuration. But, it is backwards compatible to the standard device
|
||||||
|
configuration widget.
|
||||||
|
|
||||||
|
The configuration made up of two default tabs plus extra tabs as needed for the
|
||||||
|
device. The extra tabs are defined as part of the subclass of this widget for
|
||||||
|
the device.
|
||||||
|
|
||||||
|
The two default tabs are the "File Formats" and "Extra Customization". These
|
||||||
|
tabs are the same as the two sections of the standard device configuration
|
||||||
|
widget. The second of these tabs will only be created if the device driver has
|
||||||
|
extra configuration options. All options on these tabs work the same way as for
|
||||||
|
the standard device configuration widget.
|
||||||
|
|
||||||
|
When implementing a subclass for a device driver, create tabs, subclassed from
|
||||||
|
DeviceConfigTab, for each set of options. Within the tabs, group boxes, subclassed
|
||||||
|
from DeviceOptionsGroupBox, are created to further group the options. The group
|
||||||
|
boxes can be coded to support any control type and dependencies between them.
|
||||||
|
"""
|
||||||
|
def __init__(self, device_settings, all_formats, supports_subdirs,
|
||||||
|
must_read_metadata, supports_use_author_sort,
|
||||||
|
extra_customization_message, device,
|
||||||
|
extra_customization_choices=None, parent=None):
|
||||||
|
QTabWidget.__init__(self, parent)
|
||||||
|
self._device = weakref.ref(device)
|
||||||
|
|
||||||
|
self.device_settings = device_settings
|
||||||
|
self.all_formats = set(all_formats)
|
||||||
|
self.supports_subdirs = supports_subdirs
|
||||||
|
self.must_read_metadata = must_read_metadata
|
||||||
|
self.supports_use_author_sort = supports_use_author_sort
|
||||||
|
self.extra_customization_message = extra_customization_message
|
||||||
|
self.extra_customization_choices = extra_customization_choices
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.device_name = device.get_gui_name()
|
||||||
|
except TypeError:
|
||||||
|
self.device_name = getattr(device, 'gui_name', None) or _('Device')
|
||||||
|
|
||||||
|
if device.USER_CAN_ADD_NEW_FORMATS:
|
||||||
|
self.all_formats = set(self.all_formats) | set(BOOK_EXTENSIONS)
|
||||||
|
|
||||||
|
self.base = QWidget(self)
|
||||||
|
# self.insertTab(0, self.base, _('Configure %s') % self.device.current_friendly_name)
|
||||||
|
self.insertTab(0, self.base, _("File Formats"))
|
||||||
|
l = self.base.l = QGridLayout(self.base)
|
||||||
|
self.base.setLayout(l)
|
||||||
|
|
||||||
|
self.formats = FormatsConfig(self.all_formats, device_settings.format_map)
|
||||||
|
if device.HIDE_FORMATS_CONFIG_BOX:
|
||||||
|
self.formats.hide()
|
||||||
|
|
||||||
|
self.opt_use_subdirs = create_checkbox(
|
||||||
|
_("Use sub-directories"),
|
||||||
|
_('Place files in sub-directories if the device supports them'),
|
||||||
|
device_settings.use_subdirs
|
||||||
|
)
|
||||||
|
self.opt_read_metadata = create_checkbox(
|
||||||
|
_("Read metadata from files on device"),
|
||||||
|
_('Read metadata from files on device'),
|
||||||
|
device_settings.read_metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
self.template = TemplateConfig(device_settings.save_template)
|
||||||
|
self.opt_use_author_sort = create_checkbox(
|
||||||
|
_("Use author sort for author"),
|
||||||
|
_("Use author sort for author"),
|
||||||
|
device_settings.read_metadata
|
||||||
|
)
|
||||||
|
self.opt_use_author_sort.setObjectName("opt_use_author_sort")
|
||||||
|
self.base.la = la = QLabel(_(
|
||||||
|
'Choose the formats to send to the %s')%self.device_name)
|
||||||
|
la.setWordWrap(True)
|
||||||
|
|
||||||
|
l.addWidget(la, 1, 0, 1, 1)
|
||||||
|
l.addWidget(self.formats, 2, 0, 1, 1)
|
||||||
|
l.addWidget(self.opt_read_metadata, 3, 0, 1, 1)
|
||||||
|
l.addWidget(self.opt_use_subdirs, 4, 0, 1, 1)
|
||||||
|
l.addWidget(self.opt_use_author_sort, 5, 0, 1, 1)
|
||||||
|
l.addWidget(self.template, 6, 0, 1, 1)
|
||||||
|
l.setRowStretch(2, 10)
|
||||||
|
|
||||||
|
if device.HIDE_FORMATS_CONFIG_BOX:
|
||||||
|
self.formats.hide()
|
||||||
|
|
||||||
|
if supports_subdirs:
|
||||||
|
self.opt_use_subdirs.setChecked(device_settings.use_subdirs)
|
||||||
|
else:
|
||||||
|
self.opt_use_subdirs.hide()
|
||||||
|
if not must_read_metadata:
|
||||||
|
self.opt_read_metadata.setChecked(device_settings.read_metadata)
|
||||||
|
else:
|
||||||
|
self.opt_read_metadata.hide()
|
||||||
|
if supports_use_author_sort:
|
||||||
|
self.opt_use_author_sort.setChecked(device_settings.use_author_sort)
|
||||||
|
else:
|
||||||
|
self.opt_use_author_sort.hide()
|
||||||
|
|
||||||
|
self.extra_tab = ExtraCustomization(self.extra_customization_message,
|
||||||
|
self.extra_customization_choices,
|
||||||
|
self.device_settings)
|
||||||
|
# Only display the extra customization tab if there are options on it.
|
||||||
|
if self.extra_tab.has_extra_customizations:
|
||||||
|
self.addTab(self.extra_tab, _('Extra Customization'))
|
||||||
|
|
||||||
|
self.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def addDeviceTab(self, tab, label):
|
||||||
|
'''
|
||||||
|
This is used to add a new tab for the device config. The new tab will always be added
|
||||||
|
as immediately before the "Extra Customization" tab.
|
||||||
|
'''
|
||||||
|
extra_tab_pos = self.indexOf(self.extra_tab)
|
||||||
|
self.insertTab(extra_tab_pos, tab, label)
|
||||||
|
|
||||||
|
def __getattr__(self, attr_name):
|
||||||
|
"If the object doesn't have an attribute, then check each tab."
|
||||||
|
try:
|
||||||
|
return super(TabbedDeviceConfig, self).__getattr__(attr_name)
|
||||||
|
except AttributeError as ae:
|
||||||
|
for i in range(0, self.count()):
|
||||||
|
atab = self.widget(i)
|
||||||
|
try:
|
||||||
|
return getattr(atab, attr_name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
raise ae
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device(self):
|
||||||
|
return self._device()
|
||||||
|
|
||||||
|
def format_map(self):
|
||||||
|
return self.formats.format_map
|
||||||
|
|
||||||
|
def use_subdirs(self):
|
||||||
|
return self.opt_use_subdirs.isChecked()
|
||||||
|
|
||||||
|
def read_metadata(self):
|
||||||
|
return self.opt_read_metadata.isChecked()
|
||||||
|
|
||||||
|
def use_author_sort(self):
|
||||||
|
return self.opt_use_author_sort.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opt_save_template(self):
|
||||||
|
# Really shouldn't be accessing the template this way
|
||||||
|
return self.template.t
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
# Really shouldn't be accessing the template this way
|
||||||
|
return self.template.t.text()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opt_extra_customization(self):
|
||||||
|
return self.extra_tab.opt_extra_customization
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self):
|
||||||
|
return self.opt_save_template
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
if hasattr(self, 'formats'):
|
||||||
|
if not self.formats.validate():
|
||||||
|
return False
|
||||||
|
if not self.template.validate():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
debug_print("TabbedDeviceConfig::commit: start")
|
||||||
|
p = self.device._configProxy()
|
||||||
|
|
||||||
|
p['format_map'] = self.formats.format_map
|
||||||
|
p['use_subdirs'] = self.use_subdirs()
|
||||||
|
p['read_metadata'] = self.read_metadata()
|
||||||
|
p['save_template'] = self.template.template
|
||||||
|
p['extra_customization'] = self.extra_tab.extra_customization()
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceConfigTab(QWidget): # {{{
|
||||||
|
'''
|
||||||
|
This is an abstraction for a tab in the configuration. The main reason for it is to
|
||||||
|
abstract the properties of the configuration tab. When a property is accessed, it
|
||||||
|
will iterate over all known widgets looking for the property.
|
||||||
|
'''
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QWidget.__init__(self)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
self.device_widgets = []
|
||||||
|
|
||||||
|
def addDeviceWidget(self, widget):
|
||||||
|
self.device_widgets.append(widget)
|
||||||
|
|
||||||
|
def __getattr__(self, attr_name):
|
||||||
|
try:
|
||||||
|
return super(DeviceConfigTab, self).__getattr__(attr_name)
|
||||||
|
except AttributeError as ae:
|
||||||
|
for awidget in self.device_widgets:
|
||||||
|
try:
|
||||||
|
return getattr(awidget, attr_name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
raise ae
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraCustomization(DeviceConfigTab): # {{{
|
||||||
|
|
||||||
|
def __init__(self, extra_customization_message, extra_customization_choices, device_settings):
|
||||||
|
super(ExtraCustomization, self).__init__()
|
||||||
|
|
||||||
|
debug_print("ExtraCustomization.__init__ - extra_customization_message=", extra_customization_message)
|
||||||
|
debug_print("ExtraCustomization.__init__ - extra_customization_choices=", extra_customization_choices)
|
||||||
|
debug_print("ExtraCustomization.__init__ - device_settings.extra_customization=", device_settings.extra_customization)
|
||||||
|
debug_print("ExtraCustomization.__init__ - device_settings=", device_settings)
|
||||||
|
self.extra_customization_message = extra_customization_message
|
||||||
|
|
||||||
|
self.l = QVBoxLayout(self)
|
||||||
|
self.setLayout(self.l)
|
||||||
|
|
||||||
|
options_group = QGroupBox(_("Extra driver customization options"), self)
|
||||||
|
self.l.addWidget(options_group)
|
||||||
|
self.extra_layout = QGridLayout()
|
||||||
|
self.extra_layout.setObjectName("extra_layout")
|
||||||
|
options_group.setLayout(self.extra_layout)
|
||||||
|
|
||||||
|
if extra_customization_message:
|
||||||
|
extra_customization_choices = extra_customization_choices or {}
|
||||||
|
def parse_msg(m):
|
||||||
|
msg, _, tt = m.partition(':::') if m else ('', '', '')
|
||||||
|
return msg.strip(), textwrap.fill(tt.strip(), 100)
|
||||||
|
|
||||||
|
if isinstance(extra_customization_message, list):
|
||||||
|
self.opt_extra_customization = []
|
||||||
|
if len(extra_customization_message) > 6:
|
||||||
|
row_func = lambda x, y: ((x/2) * 2) + y
|
||||||
|
col_func = lambda x: x%2
|
||||||
|
else:
|
||||||
|
row_func = lambda x, y: x*2 + y
|
||||||
|
col_func = lambda x: 0
|
||||||
|
|
||||||
|
for i, m in enumerate(extra_customization_message):
|
||||||
|
label_text, tt = parse_msg(m)
|
||||||
|
if not label_text:
|
||||||
|
self.opt_extra_customization.append(None)
|
||||||
|
continue
|
||||||
|
if isinstance(device_settings.extra_customization[i], bool):
|
||||||
|
self.opt_extra_customization.append(QCheckBox(label_text))
|
||||||
|
self.opt_extra_customization[-1].setToolTip(tt)
|
||||||
|
self.opt_extra_customization[i].setChecked(bool(device_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(device_settings.extra_customization[i])))
|
||||||
|
else:
|
||||||
|
self.opt_extra_customization.append(QLineEdit(self))
|
||||||
|
l = QLabel(label_text)
|
||||||
|
l.setToolTip(tt)
|
||||||
|
self.opt_extra_customization[i].setToolTip(tt)
|
||||||
|
l.setBuddy(self.opt_extra_customization[i])
|
||||||
|
l.setWordWrap(True)
|
||||||
|
self.opt_extra_customization[i].setText(device_settings.extra_customization[i])
|
||||||
|
self.opt_extra_customization[i].setCursorPosition(0)
|
||||||
|
self.extra_layout.addWidget(l, row_func(i + 2, 0), col_func(i))
|
||||||
|
self.extra_layout.addWidget(self.opt_extra_customization[i],
|
||||||
|
row_func(i + 2, 1), col_func(i))
|
||||||
|
spacerItem1 = QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||||
|
self.extra_layout.addItem(spacerItem1, row_func(i + 2 + 2, 1), 0, 1, 2)
|
||||||
|
self.extra_layout.setRowStretch(row_func(i + 2 + 2, 1), 2)
|
||||||
|
else:
|
||||||
|
self.opt_extra_customization = QLineEdit()
|
||||||
|
label_text, tt = parse_msg(extra_customization_message)
|
||||||
|
l = QLabel(label_text)
|
||||||
|
l.setToolTip(tt)
|
||||||
|
l.setBuddy(self.opt_extra_customization)
|
||||||
|
l.setWordWrap(True)
|
||||||
|
if device_settings.extra_customization:
|
||||||
|
self.opt_extra_customization.setText(device_settings.extra_customization)
|
||||||
|
self.opt_extra_customization.setCursorPosition(0)
|
||||||
|
self.opt_extra_customization.setCursorPosition(0)
|
||||||
|
self.extra_layout.addWidget(l, 0, 0)
|
||||||
|
self.extra_layout.addWidget(self.opt_extra_customization, 1, 0)
|
||||||
|
|
||||||
|
def extra_customization(self):
|
||||||
|
ec = []
|
||||||
|
if self.extra_customization_message:
|
||||||
|
if isinstance(self.extra_customization_message, list):
|
||||||
|
for i in range(0, len(self.extra_customization_message)):
|
||||||
|
if self.opt_extra_customization[i] is None:
|
||||||
|
ec.append(None)
|
||||||
|
continue
|
||||||
|
if hasattr(self.opt_extra_customization[i], 'isChecked'):
|
||||||
|
ec.append(self.opt_extra_customization[i].isChecked())
|
||||||
|
elif hasattr(self.opt_extra_customization[i], 'currentText'):
|
||||||
|
ec.append(unicode(self.opt_extra_customization[i].currentText()).strip())
|
||||||
|
else:
|
||||||
|
ec.append(unicode(self.opt_extra_customization[i].text()).strip())
|
||||||
|
else:
|
||||||
|
ec = unicode(self.opt_extra_customization.text()).strip()
|
||||||
|
if not ec:
|
||||||
|
ec = None
|
||||||
|
|
||||||
|
return ec
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_extra_customizations(self):
|
||||||
|
debug_print("ExtraCustomization::has_extra_customizations - self.extra_customization_message", self.extra_customization_message)
|
||||||
|
return self.extra_customization_message and len(self.extra_customization_message) > 0
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class DeviceOptionsGroupBox(QGroupBox):
|
||||||
|
"""
|
||||||
|
This is a container for the individual options for a device driver.
|
||||||
|
"""
|
||||||
|
def __init__(self, parent, device=None, title=_("Unknown")):
|
||||||
|
QGroupBox.__init__(self, parent)
|
||||||
|
|
||||||
|
self.device = device
|
||||||
|
self.setTitle(title)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
from calibre.devices.kobo.driver import KOBO
|
||||||
|
from calibre.devices.scanner import DeviceScanner
|
||||||
|
s = DeviceScanner()
|
||||||
|
s.scan()
|
||||||
|
app = Application([])
|
||||||
|
dev = KOBO(None)
|
||||||
|
debug_print("KOBO:", KOBO)
|
||||||
|
# dev.startup()
|
||||||
|
# cd = dev.detect_managed_devices(s.devices)
|
||||||
|
# dev.open(cd, 'test')
|
||||||
|
cw = dev.config_widget()
|
||||||
|
d = QDialog()
|
||||||
|
d.l = QVBoxLayout()
|
||||||
|
d.setLayout(d.l)
|
||||||
|
d.l.addWidget(cw)
|
||||||
|
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
d.l.addWidget(bb)
|
||||||
|
bb.accepted.connect(d.accept)
|
||||||
|
bb.rejected.connect(d.reject)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
cw.commit()
|
||||||
|
dev.shutdown()
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user