%PDF- %PDF-
Direktori : /usr/lib/calibre/calibre/devices/kobo/ |
Current File : //usr/lib/calibre/calibre/devices/kobo/kobotouch_config.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2015-2019, Kovid Goyal <kovid at kovidgoyal.net>' __docformat__ = 'restructuredtext en' import textwrap from qt.core import (QWidget, QLabel, QGridLayout, QLineEdit, QVBoxLayout, QDialog, QDialogButtonBox, QCheckBox, QPushButton) from calibre.gui2.device_drivers.tabbed_device_config import TabbedDeviceConfig, DeviceConfigTab, DeviceOptionsGroupBox from calibre.devices.usbms.driver import debug_print from calibre.gui2 import error_dialog from calibre.gui2.widgets2 import ColorButton from calibre.gui2.dialogs.template_dialog import TemplateDialog 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().__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): validated = super().validate() validated &= self.tab2.validate() return validated @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().commit() p['manage_collections'] = self.manage_collections p['create_collections'] = self.create_collections p['collections_columns'] = self.collections_columns p['ignore_collections_names'] = self.ignore_collections_names 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['dithered_covers'] = self.dithered_covers p['letterbox_fs_covers'] = self.letterbox_fs_covers p['letterbox_fs_covers_color'] = self.letterbox_fs_covers_color p['png_covers'] = self.png_covers p['show_recommendations'] = self.show_recommendations p['show_previews'] = self.show_previews p['show_archived_books'] = self.show_archived_books p['update_device_metadata'] = self.update_device_metadata p['update_series'] = self.update_series p['update_core_metadata'] = self.update_core_metadata p['update_purchased_kepubs'] = self.update_purchased_kepubs p['subtitle_template'] = self.subtitle_template p['update_subtitle'] = self.update_subtitle p['modify_css'] = self.modify_css p['override_kobo_replace_existing'] = self.override_kobo_replace_existing p['support_newer_firmware'] = self.support_newer_firmware p['debugging_title'] = self.debugging_title p['driver_version'] = '.'.join([str(i) for i in self.device.version]) return p class Tab1Config(DeviceConfigTab): # {{{ def __init__(self, parent, device): super().__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) self.l.addStretch() # }}} class Tab2Config(DeviceConfigTab): # {{{ def __init__(self, parent, device): super().__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) self.l.addStretch() def validate(self): return self.metadata_options.validate() # }}} class BookUploadsGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super().__init__(parent, device) self.setTitle(_("Uploading of books")) 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 folder 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.override_kobo_replace_existing_checkbox = create_checkbox( _("Do not treat replacements as new books"), _('When a new book is side-loaded, the Kobo firmware imports details of the book into the internal database. ' 'Even if the book is a replacement for an existing book, the Kobo will remove the book from the database and then treat it as a new book. ' 'This means that the reading status, bookmarks and collections for the book will be lost. ' 'This option overrides firmware behavior and attempts to prevent a book that has been resent from being treated as a new book. ' 'If you prefer to have replacements treated as new books, turn this option off.' ), device.get_pref('override_kobo_replace_existing') ) self.options_layout.addWidget(self.modify_css_checkbox, 0, 0, 1, 2) self.options_layout.addWidget(self.override_kobo_replace_existing_checkbox, 1, 0, 1, 2) @property def modify_css(self): return self.modify_css_checkbox.isChecked() @property def override_kobo_replace_existing(self): return self.override_kobo_replace_existing_checkbox.isChecked() class CollectionsGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super().__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.ignore_collections_names_label = QLabel(_('Ignore collections:')) self.ignore_collections_names_edit = QLineEdit(self) self.ignore_collections_names_edit.setToolTip(_('List the names of collections to be ignored by ' 'the collection management. The collections listed ' 'will not be changed. Names are separated by commas.')) self.ignore_collections_names_edit.setText(device.get_pref('ignore_collections_names')) 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.addWidget(self.ignore_collections_names_label, 4, 0, 1, 1) self.options_layout.addWidget(self.ignore_collections_names_edit, 4, 1, 1, 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() @property def ignore_collections_names(self): return self.ignore_collections_names_edit.text().strip() class CoversGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super().__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 grayscale when uploading.'), device.get_pref('upload_grayscale') ) self.dithered_covers_checkbox = create_checkbox( _('Upload dithered covers'), _('Dither cover images to the appropriate 16c grayscale palette for an eInk screen.' ' This usually ensures greater accuracy and avoids banding, making sleep covers look better.' ' On FW >= 4.11, Nickel itself may sometimes do a decent job of it.' ' Has no effect without "Upload black and white covers"!'), device.get_pref('dithered_covers') ) # Make it visually depend on B&W being enabled! # c.f., https://stackoverflow.com/q/36281103 self.dithered_covers_checkbox.setEnabled(device.get_pref('upload_grayscale')) self.upload_grayscale_checkbox.toggled.connect(self.dithered_covers_checkbox.setEnabled) self.upload_grayscale_checkbox.toggled.connect( lambda checked: not checked and self.dithered_covers_checkbox.setChecked(False)) 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.letterbox_fs_covers_checkbox = create_checkbox( _('Letterbox full-screen covers'), _('Do it on our end, instead of letting Nickel handle it.' ' Provides pixel-perfect results on devices where Nickel does not do extra processing.' ' Obviously has no effect without "Keep cover aspect ratio".' ' This is probably undesirable if you disable the "Show book covers full screen"' ' setting on your device.'), device.get_pref('letterbox_fs_covers')) self.letterbox_fs_covers_color_button = ColorButton(self.options_layout) self.letterbox_fs_covers_color_button.setToolTip(_('Choose the color to use when letterboxing the cover.' ' The default color is black (#000000)' ) ) self.letterbox_fs_covers_color_button.color = device.get_pref('letterbox_fs_covers_color') # Make it visually depend on AR being enabled! self.letterbox_fs_covers_checkbox.setEnabled(device.get_pref('keep_cover_aspect')) self.letterbox_fs_covers_color_button.setEnabled(device.get_pref('keep_cover_aspect') and device.get_pref('letterbox_fs_covers')) self.keep_cover_aspect_checkbox.toggled.connect(self.letterbox_fs_covers_checkbox.setEnabled) self.keep_cover_aspect_checkbox.toggled.connect( lambda checked: not checked and self.letterbox_fs_covers_checkbox.setChecked(False)) self.letterbox_fs_covers_checkbox.toggled.connect(self.letterbox_fs_covers_color_button.setEnabled) self.png_covers_checkbox = create_checkbox( _('Save covers as PNG'), _('Use the PNG image format instead of JPG.' ' Higher quality, especially with "Upload dithered covers" enabled,' ' which will also help generate potentially smaller files.' ' Behavior completely unknown on old (< 3.x) Kobo firmwares,' ' known to behave on FW >= 4.8.' ' Has no effect without "Upload black and white covers"!'), device.get_pref('png_covers')) # Make it visually depend on B&W being enabled, to avoid storing ridiculously large color PNGs. self.png_covers_checkbox.setEnabled(device.get_pref('upload_grayscale')) self.upload_grayscale_checkbox.toggled.connect(self.png_covers_checkbox.setEnabled) self.upload_grayscale_checkbox.toggled.connect( lambda checked: not checked and self.png_covers_checkbox.setChecked(False)) self.options_layout.addWidget(self.keep_cover_aspect_checkbox, 0, 0, 1, 1) self.options_layout.addWidget(self.letterbox_fs_covers_checkbox, 0, 1, 1, 2) self.options_layout.addWidget(self.letterbox_fs_covers_color_button, 1, 1, 1, 1) self.options_layout.addWidget(self.upload_grayscale_checkbox, 2, 0, 1, 1) self.options_layout.addWidget(self.dithered_covers_checkbox, 2, 1, 1, 2) self.options_layout.addWidget(self.png_covers_checkbox, 3, 1, 1, 2) self.options_layout.setColumnStretch(0, 0) self.options_layout.setColumnStretch(1, 0) self.options_layout.setColumnStretch(2, 1) @property def upload_covers(self): return self.isChecked() @property def upload_grayscale(self): return self.upload_grayscale_checkbox.isChecked() @property def dithered_covers(self): return self.dithered_covers_checkbox.isChecked() @property def keep_cover_aspect(self): return self.keep_cover_aspect_checkbox.isChecked() @property def letterbox_fs_covers(self): return self.letterbox_fs_covers_checkbox.isChecked() @property def letterbox_fs_covers_color(self): return self.letterbox_fs_covers_color_button.color @property def png_covers(self): return self.png_covers_checkbox.isChecked() class DeviceListGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super().__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) @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().__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 ') + str( 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) @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().__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.update_core_metadata_checkbox = create_checkbox( _("Update metadata on Book Details pages"), _('This will update the metadata in the device database when the device is connected. ' 'The metadata updated is displayed on the device in the library and the book details page. ' 'This is the title, authors, comments/synopsis, series name and number, publisher and published Date, ISBN and language. ' 'If a metadata plugboard exists for the device and book format, this will be used to set the metadata.' ), device.get_pref('update_core_metadata') ) self.update_purchased_kepubs_checkbox = create_checkbox( _("Update purchased books"), _('Update books purchased from Kobo and downloaded to the device.' ), device.get_pref('update_purchased_kepubs') ) self.update_subtitle_checkbox = create_checkbox( _("Subtitle"), _('Update the subtitle on the device using a template.'), device.get_pref('update_subtitle') ) self.subtitle_template_edit = TemplateConfig( device.get_pref('subtitle_template'), tooltip=_("Enter a template to use to set the subtitle. " "If the template is empty, the subtitle will be cleared." ) ) self.options_layout.addWidget(self.update_series_checkbox, 0, 0, 1, 2) self.options_layout.addWidget(self.update_core_metadata_checkbox, 1, 0, 1, 2) self.options_layout.addWidget(self.update_subtitle_checkbox, 2, 0, 1, 1) self.options_layout.addWidget(self.subtitle_template_edit, 2, 1, 1, 1) self.options_layout.addWidget(self.update_purchased_kepubs_checkbox, 3, 0, 1, 2) self.update_core_metadata_checkbox.clicked.connect(self.update_core_metadata_checkbox_clicked) self.update_subtitle_checkbox.clicked.connect(self.update_subtitle_checkbox_clicked) self.update_core_metadata_checkbox_clicked(device.get_pref('update_core_metadata')) self.update_subtitle_checkbox_clicked(device.get_pref('update_subtitle')) def update_core_metadata_checkbox_clicked(self, checked): self.update_series_checkbox.setEnabled(not checked) self.subtitle_template_edit.setEnabled(checked) self.update_subtitle_checkbox.setEnabled(checked) self.update_subtitle_checkbox_clicked(self.update_subtitle) self.update_purchased_kepubs_checkbox.setEnabled(checked) def update_subtitle_checkbox_clicked(self, checked): self.subtitle_template_edit.setEnabled(checked and self.update_core_metadata) def edit_template(self): t = TemplateDialog(self, self.template) t.setWindowTitle(_('Edit template')) if t.exec(): self.t.setText(t.rule[1]) def validate(self): if self.update_subtitle and not self.subtitle_template_edit.validate(): return False return True @property def update_series(self): return self.update_series_checkbox.isChecked() @property def update_core_metadata(self): return self.update_core_metadata_checkbox.isChecked() @property def update_purchased_kepubs(self): return self.update_purchased_kepubs_checkbox.isChecked() @property def update_device_metadata(self): return self.isChecked() @property def subtitle_template(self): return self.subtitle_template_edit.template @property def update_subtitle(self): return self.update_subtitle_checkbox.isChecked() class TemplateConfig(QWidget): # {{{ def __init__(self, val, tooltip=None): QWidget.__init__(self) self.t = t = QLineEdit(self) t.setText(val or '') t.setCursorPosition(0) self.setMinimumWidth(300) self.l = l = QGridLayout(self) self.setLayout(l) l.addWidget(t, 1, 0, 1, 1) b = self.b = QPushButton(_('&Template editor')) l.addWidget(b, 1, 1, 1, 1) b.clicked.connect(self.edit_template) self.setToolTip(tooltip) @property def template(self): return str(self.t.text()).strip() @template.setter def template(self, template): self.t.setText(template) def edit_template(self): t = TemplateDialog(self, self.template) t.setWindowTitle(_('Edit template')) if t.exec(): self.t.setText(t.rule[1]) def validate(self): from calibre.utils.formatter import validation_formatter tmpl = self.template try: validation_formatter.validate(tmpl) return True except Exception as err: error_dialog(self, _('Invalid template'), '<p>'+_('The template "%s" is invalid:')%tmpl + '<br>'+str(err), show=True) return False # }}} 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.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel) d.l.addWidget(bb) bb.accepted.connect(d.accept) bb.rejected.connect(d.reject) if d.exec() == QDialog.DialogCode.Accepted: cw.commit() dev.shutdown()