From adc9535714843aae1eaaa992ac29b9d8ca2bc54d Mon Sep 17 00:00:00 2001 From: David Date: Mon, 11 Apr 2016 20:36:34 +1000 Subject: [PATCH] New tabbed configuration for KoboTouch driver. With the new files. --- src/calibre/devices/kobo/kobotouch_config.py | 486 ++++++++++++++++++ .../device_drivers/tabbed_device_config.py | 408 +++++++++++++++ 2 files changed, 894 insertions(+) create mode 100644 src/calibre/devices/kobo/kobotouch_config.py create mode 100644 src/calibre/gui2/device_drivers/tabbed_device_config.py diff --git a/src/calibre/devices/kobo/kobotouch_config.py b/src/calibre/devices/kobo/kobotouch_config.py new file mode 100644 index 0000000000..e413eabc84 --- /dev/null +++ b/src/calibre/devices/kobo/kobotouch_config.py @@ -0,0 +1,486 @@ +#!/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 ' +__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.current_device_key = device.device_defaults_key + + self.tab1 = Tab1Config(self, self.device) + self.tab2 = Tab2Config(self, self.device) + + extra_tab_pos = self.indexOf(self.extra_tab) + last_tab_pos = self.insertTab(extra_tab_pos, self.tab1, _("Collections, Covers && Uploads")) + last_tab_pos = self.insertTab(last_tab_pos + 1, self.tab2, _('Metadata && 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 + + p['extra_customization'] = self.extra_tab.extra_customization() + + 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.add_widget(self.collections_options) + + self.covers_options = CoversGroupBox(self, device) + self.l.addWidget(self.covers_options) + self.add_widget(self.covers_options) + + self.book_uploads_options = BookUploadsGroupBox(self, device) + self.l.addWidget(self.book_uploads_options) + self.add_widget(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.add_widget(self.metadata_options) + + self.device_list_options = DeviceListGroupBox(self, device) + self.l.addWidget(self.device_list_options) + self.add_widget(self.device_list_options) + + self.advanced_options = AdvancedGroupBox(self, device) + self.l.addWidget(self.advanced_options) + self.add_widget(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.manage_collections_checkbox = create_checkbox( + _("Manage 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('manage_collections') + ) + self.manage_collections_checkbox.clicked.connect(self.manage_collections_checkbox_clicked) + + 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.')) + debug_print("CollectionsGroupBox::__init__ - device.settings()=", device.settings()) + 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.manage_collections_checkbox, 0, 0, 1, 1) + 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, 1) + self.options_layout.addWidget(self.delete_empty_collections_checkbox, 2, 1, 1, 1) + self.options_layout.setRowStretch(5, 1) + + self.manage_collections_checkbox_clicked(self.manage_collections) + + @property + def manage_collections(self): + return self.manage_collections_checkbox.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() + + def manage_collections_checkbox_clicked(self, checked): + self.collections_columns_label.setEnabled(checked) + self.collections_columns_edit.setEnabled(checked) + self.create_collections_checkbox.setEnabled(checked) + self.delete_empty_collections_checkbox.setEnabled(checked) + + +class CoversGroupBox(DeviceOptionsGroupBox): + + def __init__(self, parent, device): + super(CoversGroupBox, self).__init__(parent, device) + self.setTitle(_("Covers")) + + self.options_layout = QGridLayout() + self.options_layout.setObjectName("options_layout") + self.setLayout(self.options_layout) + + self.upload_covers_checkbox = create_checkbox( + _("Upload covers for books"), + _('Upload cover images from the calibre library when sending books to the device.'), + device.get_pref('upload_covers') + ) + self.upload_covers_checkbox.clicked.connect(self.upload_covers_checkbox_clicked) + + 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.upload_covers_checkbox, 0, 0, 1, 2) + self.options_layout.addWidget(self.keep_cover_aspect_checkbox, 1, 0, 1, 1) + self.options_layout.addWidget(self.upload_grayscale_checkbox, 1, 1, 1, 1) + self.options_layout.setRowStretch(2, 1) + + self.upload_covers_checkbox_clicked(self.upload_covers) + + @property + def upload_covers(self): + return self.upload_covers_checkbox.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() + + def upload_covers_checkbox_clicked(self, checked): + self.upload_grayscale_checkbox.setEnabled(checked) + self.keep_cover_aspect_checkbox.setEnabled(checked) + + +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, 0, 1, 1, 1) + self.options_layout.addWidget(self.show_previews_checkbox, 1, 0, 1, 1) + self.options_layout.setRowStretch(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(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(1, 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(_("Metadata Options")) + + self.options_layout = QGridLayout() + self.options_layout.setObjectName("options_layout") + self.setLayout(self.options_layout) + + self.update_device_metadata_checkbox = create_checkbox( + _("Update metadata on the device"), + _('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.'), + device.get_pref('update_device_metadata') + ) + self.options_layout.addWidget(self.update_device_metadata_checkbox, 0, 0, 1, 2) + self.update_device_metadata_checkbox.clicked.connect(self.update_device_metadata_checkbox_clicked) + + 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, 1, 0, 1, 2) + 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.update_series_checkbox.isChecked() + + def update_device_metadata_checkbox_clicked(self, checked): + self.update_series_checkbox.setEnabled(checked) + + +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() + + diff --git a/src/calibre/gui2/device_drivers/tabbed_device_config.py b/src/calibre/gui2/device_drivers/tabbed_device_config.py new file mode 100644 index 0000000000..0b20c168d8 --- /dev/null +++ b/src/calibre/gui2/device_drivers/tabbed_device_config.py @@ -0,0 +1,408 @@ +#!/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 ' +__docformat__ = 'restructuredtext en' + +import weakref, textwrap + +from PyQt5.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel, + QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout, + QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox, + pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox, QPlainTextEdit, + QApplication, QSize, 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 __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 + + def get_pref(self, key): + debug_print("get_pref - self.device.prefs", self.device.prefs) + p = self.device.prefs.get(self.current_device_key, {}) + if not p: + self.device.prefs[self.current_device_key] = p + debug_print("get_pref - self.device.get_pref(key)", self.device.get_pref(key)) + return self.device.get_pref(key) + + @property + def device(self): + return self._device() + + def format_map(self): +# formats = [unicode(self.columns.item(i).data(Qt.UserRole) or '') for i in range(self.columns.count()) if self.columns.item(i).checkState()==Qt.Checked] + 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() + debug_print("commit: starting setting=%s" % (p, )) + + f = self.formats.format_map + debug_print("commit: self.formats.format_map=", self.formats.format_map) + debug_print("commit: self.device.prefs['format_map']=", self.device.prefs['format_map']) + if f and f != self.device.prefs['format_map']: + p['format_map'] = f + + f = self.use_subdirs() + if f != self.get_pref('use_subdirs'): + p['use_subdirs'] = f + + f = self.read_metadata() + if f != self.get_pref('read_metadata'): + p['read_metadata'] = f + + t = self.template.template + if t and t != self.device.prefs['save_template']: + p['save_template'] = t + + 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.widgets = [] + + def add_widget(self, widget): + self.widgets.append(widget) + + def __getattr__(self, attr_name): + try: + return super(DeviceConfigTab, self).__getattr__(attr_name) + except AttributeError as ae: + for awidget in self.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() + +