class DaysOfMonth(Base): HELP = _('''\ Download this periodical every month, on the specified days. The download will happen as soon after the specified time as possible on the specified days of each month. For example, if you choose the 1st and the 15th after 9:00 AM, the periodical will be downloaded on the 1st and 15th of every month, as soon after 9:00 AM as possible. ''') def __init__(self, parent=None): Base.__init__(self, parent) self.l1 = QLabel(_('&Days of the month:')) self.days = QLineEdit(self) self.days.setToolTip( _('Comma separated list of days of the month.' ' For example: 1, 15')) self.l1.setBuddy(self.days) self.l2 = QLabel(_('Download &after:')) self.time = QTimeEdit(self) self.time.setDisplayFormat('hh:mm AP') self.l2.setBuddy(self.time) self.l.addWidget(self.l1, 0, 0, 1, 1) self.l.addWidget(self.days, 0, 1, 1, 1) self.l.addWidget(self.l2, 1, 0, 1, 1) self.l.addWidget(self.time, 1, 1, 1, 1) def initialize(self, typ=None, val=None): if val is None: val = ((1, ), 6, 0) days_of_month, hour, minute = val self.days.setText(', '.join(map(str, map(int, days_of_month)))) self.time.setTime(QTime(hour, minute)) @property def schedule(self): parts = [ x.strip() for x in unicode(self.days.text()).split(',') if x.strip() ] try: days_of_month = tuple(map(int, parts)) except: days_of_month = (1, ) if not days_of_month: days_of_month = (1, ) t = self.time.time() hour, minute = t.hour(), t.minute() return 'days_of_month', (days_of_month, int(hour), int(minute))
def create_widgets(self, opt): val = self.plugin.prefs[opt.name] if opt.type == 'number': c = QSpinBox if isinstance(opt.default, int) else QDoubleSpinBox widget = c(self) widget.setValue(val) elif opt.type == 'string': widget = QLineEdit(self) widget.setText(val if val else '') elif opt.type == 'bool': widget = QCheckBox(opt.label, self) widget.setChecked(bool(val)) elif opt.type == 'choices': widget = QComboBox(self) items = list(opt.choices.iteritems()) items.sort(key=lambda (k, v): sort_key(v)) for key, label in items: widget.addItem(label, (key)) idx = widget.findData((val)) widget.setCurrentIndex(idx) widget.opt = opt widget.setToolTip(textwrap.fill(opt.desc)) self.widgets.append(widget) r = self.l.rowCount() if opt.type == 'bool': self.l.addWidget(widget, r, 0, 1, self.l.columnCount()) else: l = QLabel(opt.label) l.setToolTip(widget.toolTip()) self.memory.append(l) l.setBuddy(widget) self.l.addWidget(l, r, 0, 1, 1) self.l.addWidget(widget, r, 1, 1, 1)
class RenameKeyDialog(QDialog): def __init__(self, parent=None): print repr(self), repr(parent) QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle("{0} {1}: Rename {0}".format(PLUGIN_NAME, PLUGIN_VERSION, parent.key_type_name)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox("", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) data_group_box_layout.addWidget(QLabel("New Key Name:", self)) self.key_ledit = QLineEdit(self.parent.listy.currentItem().text(), self) self.key_ledit.setToolTip(u"Enter a new name for this existing {0}.".format(parent.key_type_name)) data_group_box_layout.addWidget(self.key_ledit) layout.addSpacing(20) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) def accept(self): if not unicode(self.key_ledit.text()) or unicode(self.key_ledit.text()).isspace(): errmsg = u"Key name field cannot be empty!" return error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False ) if len(self.key_ledit.text()) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False ) if uStrCmp(self.key_ledit.text(), self.parent.listy.currentItem().text()): # Same exact name ... do nothing. return QDialog.reject(self) for k in self.parent.plugin_keys.keys(): if uStrCmp(self.key_ledit.text(), k, True) and not uStrCmp(k, self.parent.listy.currentItem().text(), True): errmsg = u"The key name <strong>{0}</strong> is already being used.".format(self.key_ledit.text()) return error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), _(errmsg), show=True, show_copy_button=False ) QDialog.accept(self) @property def key_name(self): return unicode(self.key_ledit.text()).strip()
class AddSerialDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Add New EInk Kindle Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"EInk Kindle Serial Number:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip( u"Enter an eInk Kindle serial number. EInk Kindle serial numbers are 16 characters long and usually start with a 'B' or a '9'. Kindle Serial Numbers are case-sensitive, so be sure to enter the upper and lower case letters unchanged." ) key_group.addWidget(self.key_ledit) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): return unicode(self.key_ledit.text()).strip() def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): errmsg = u"Please enter an eInk Kindle Serial Number or click Cancel in the dialog." return error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False ) if len(self.key_name) != 16: errmsg = u"EInk Kindle Serial Numbers must be 16 characters long. This is {0:d} characters long.".format( len(self.key_name) ) return error_dialog( None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False ) QDialog.accept(self)
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 AddPIDDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Add New Mobipocket PID".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"PID:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(u"Enter a Mobipocket PID. Mobipocket PIDs are 8 or 10 characters long. Mobipocket PIDs are case-sensitive, so be sure to enter the upper and lower case letters unchanged.") key_group.addWidget(self.key_ledit) key_label = QLabel(_(''), self) key_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(key_label) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): return unicode(self.key_ledit.text()).strip() def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): errmsg = u"Please enter a Mobipocket PID or click Cancel in the dialog." return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) != 8 and len(self.key_name) != 10: errmsg = u"Mobipocket PIDs must be 8 or 10 characters long. This is {0:d} characters long.".format(len(self.key_name)) return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
def __init__(self, parent=None): QDialog.__init__(self, parent) self._layout = l = QGridLayout(self) self.setLayout(l) self.setWindowIcon(QIcon(I('mail.png'))) self.setWindowTitle(_('Select recipients')) self.recipients = r = QListWidget(self) l.addWidget(r, 0, 0, 1, -1) self.la = la = QLabel(_('Add a new recipient:')) la.setStyleSheet('QLabel { font-weight: bold }') l.addWidget(la, l.rowCount(), 0, 1, -1) self.labels = tuple(map(QLabel, ( _('&Address'), _('A&lias'), _('&Formats'), _('&Subject')))) tooltips = ( _('The email address of the recipient'), _('The optional alias (simple name) of the recipient'), _('Formats to email. The first matching one will be sent (comma separated list)'), _('The optional subject for email sent to this recipient')) for i, name in enumerate(('address', 'alias', 'formats', 'subject')): c = i % 2 row = l.rowCount() - c self.labels[i].setText(unicode(self.labels[i].text()) + ':') l.addWidget(self.labels[i], row, (2*c)) le = QLineEdit(self) le.setToolTip(tooltips[i]) setattr(self, name, le) self.labels[i].setBuddy(le) l.addWidget(le, row, (2*c) + 1) self.formats.setText(prefs['output_format'].upper()) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('&Add recipient'), self) b.clicked.connect(self.add_recipient) l.addWidget(b, l.rowCount(), 0, 1, -1) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) l.addWidget(bb, l.rowCount(), 0, 1, -1) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.setMinimumWidth(500) self.setMinimumHeight(400) self.resize(self.sizeHint()) self.init_list()
class AddAndroidSerialDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Add New Kindle for Android Serial Number".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Kindle for Android Serial Number:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(u"Enter a Kindle for ANdroid serial number. These can be found using the androidkindlekey.py script.") key_group.addWidget(self.key_ledit) key_label = QLabel(_(''), self) key_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(key_label) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): return unicode(self.key_ledit.text()).strip() def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): errmsg = u"Please enter a Kindle for Android Serial Number or click Cancel in the dialog." return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class SearchesTab(QWidget): def __init__(self, parent_dialog, plugin_action): QWidget.__init__(self) self.parent_dialog = parent_dialog self.plugin_action = plugin_action self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_('Searches to use for:')) label.setWordWrap(True) self.l.addWidget(label) #self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.sl.addWidget(QLabel(_("Search for Duplicated Books:"))) self.checkdups_search = QLineEdit(self) self.sl.addWidget(self.checkdups_search) self.checkdups_search.setText(prefs['checkdups_search']) self.checkdups_search.setToolTip(_('Default is %s')%default_prefs['checkdups_search']) self.sl.addSpacing(5) self.sl.addWidget(QLabel(_("Deleted Books (not in Library):"))) self.checknotinlibrary_search = QLineEdit(self) self.sl.addWidget(self.checknotinlibrary_search) self.checknotinlibrary_search.setText(prefs['checknotinlibrary_search']) self.checknotinlibrary_search.setToolTip(_('Default is %s')%default_prefs['checknotinlibrary_search']) self.sl.addSpacing(5) self.sl.addWidget(QLabel(_("Added Books (not on Device):"))) self.checknotondevice_search = QLineEdit(self) self.sl.addWidget(self.checknotondevice_search) self.checknotondevice_search.setText(prefs['checknotondevice_search']) self.checknotondevice_search.setToolTip(_('Default is %s')%default_prefs['checknotondevice_search']) self.sl.insertStretch(-1) self.l.addSpacing(15) restore_defaults_button = QPushButton(_('Restore Defaults'), self) restore_defaults_button.setToolTip(_('Revert all searches to the defaults.')) restore_defaults_button.clicked.connect(self.restore_defaults_button) self.l.addWidget(restore_defaults_button) def restore_defaults_button(self): self.checkdups_search.setText(default_prefs['checkdups_search']) self.checknotinlibrary_search.setText(default_prefs['checknotinlibrary_search']) self.checknotondevice_search.setText(default_prefs['checknotondevice_search'])
class ExtendedGroupBox(DeviceOptionsGroupBox): """The options group for KoboTouchExtended.""" def __init__(self, parent, device): """Set up driver config options group.""" super(ExtendedGroupBox, self).__init__( parent, device, _("Extended driver") # noqa: F821 ) self.options_layout = QGridLayout() self.options_layout.setObjectName("options_layout") self.setLayout(self.options_layout) self.extra_features_checkbox = create_checkbox( _("Enable Extended Kobo Features"), # noqa: F821 _("Choose whether to enable extra customizations"), # noqa: F821 device.get_pref("extra_features"), ) self.upload_encumbered_checkbox = create_checkbox( _("Upload DRM-encumbered ePub files"), # noqa: F821 _( # noqa: F821 "Select this to upload ePub files encumbered by DRM. If this " "is not selected, it is a fatal error to upload an encumbered " "file" ), device.get_pref("upload_encumbered"), ) self.skip_failed_checkbox = create_checkbox( _("Silently Ignore Failed Conversions"), # noqa: F821 _( # noqa: F821 "Select this to not upload any book that fails conversion to " "kepub. If this is not selected, the upload process will be " "stopped at the first book that fails. If this is selected, " "failed books will be silently removed from the upload queue." ), device.get_pref("skip_failed"), ) self.hyphenate_checkbox = create_checkbox( _("Hyphenate Files"), # noqa: F821 _( # noqa: F821 "Select this to add a CSS file which enables hyphenation. The " "language used will be the language defined for the book in " "calibre. Please see the README file for directions on " "updating hyphenation dictionaries." ), device.get_pref("hyphenate"), ) self.smarten_punctuation_checkbox = create_checkbox( _("Smarten Punctuation"), # noqa: F821 _("Select this to smarten punctuation in the ePub"), # noqa: F821 device.get_pref("smarten_punctuation"), ) self.clean_markup_checkbox = create_checkbox( _("Clean up ePub Markup"), # noqa: F821 _("Select this to clean up the internal ePub markup."), # noqa: F821 device.get_pref("clean_markup"), ) self.file_copy_dir_checkbox = create_checkbox( _("Copy generated KePub files to a directory"), # noqa: F821 _( # noqa: F821 "Enter an absolute directory path to copy all generated KePub " "files into for debugging purposes." ), device.get_pref("file_copy_dir"), ) self.file_copy_dir_label = QLabel( _("Copy generated KePub files to a directory") # noqa: F821 ) self.file_copy_dir_edit = QLineEdit(self) self.file_copy_dir_edit.setToolTip( _( # noqa: F821 "Enter an absolute directory path to copy all generated KePub " "files into for debugging purposes." ) ) self.file_copy_dir_edit.setText(device.get_pref("file_copy_dir")) self.file_copy_dir_label.setBuddy(self.file_copy_dir_edit) self.full_page_numbers_checkbox = create_checkbox( _("Use full book page numbers"), # noqa: F821 _( # noqa: F821 "Select this to show page numbers for the whole book, instead " "of each chapter. This will also affect regular ePub page " "number display!" ), device.get_pref("full_page_numbers"), ) self.disable_hyphenation_checkbox = create_checkbox( _("Disable hyphenation"), # noqa: F821 _("Select this to disable hyphenation for books."), # noqa: F821 device.get_pref("disable_hyphenation"), ) self.options_layout.addWidget(self.extra_features_checkbox, 0, 0, 1, 1) self.options_layout.addWidget(self.upload_encumbered_checkbox, 0, 1, 1, 1) self.options_layout.addWidget(self.skip_failed_checkbox, 1, 0, 1, 1) self.options_layout.addWidget(self.hyphenate_checkbox, 1, 1, 1, 1) self.options_layout.addWidget(self.smarten_punctuation_checkbox, 2, 1, 1, 1) self.options_layout.addWidget(self.clean_markup_checkbox, 3, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_label, 4, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_edit, 4, 1, 1, 1) self.options_layout.addWidget(self.full_page_numbers_checkbox, 5, 0, 1, 1) self.options_layout.addWidget(self.disable_hyphenation_checkbox, 5, 1, 1, 1) self.options_layout.setRowStretch(6, 2) @property def extra_features(self): """Determine if Kobo extra features are enabled.""" return self.extra_features_checkbox.isChecked() @property def upload_encumbered(self): """Determine if DRM-encumbered files will be uploaded.""" return self.upload_encumbered_checkbox.isChecked() @property def skip_failed(self): """Determine if failed conversions will be skipped.""" return self.skip_failed_checkbox.isChecked() @property def hyphenate(self): """Determine if hyphenation should be enabled.""" return self.hyphenate_checkbox.isChecked() @property def smarten_punctuation(self): """Determine if punctuation should be converted to smart punctuation.""" return self.smarten_punctuation_checkbox.isChecked() @property def clean_markup(self): """Determine if additional markup cleanup will be done.""" return self.clean_markup_checkbox.isChecked() @property def full_page_numbers(self): """Determine if full-book page numbers will be displayed.""" return self.full_page_numbers_checkbox.isChecked() @property def disable_hyphenation(self): """Determine if hyphenation should be disabled.""" return self.disable_hyphenation_checkbox.isChecked() @property def file_copy_dir(self): """Determine where to copy converted KePub books to.""" return self.file_copy_dir_edit.text().strip()
class AddBandNKeyDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" + u"<p>It should be something that will help you remember " + u"what personal information was used to create it.")) key_group.addWidget(self.key_ledit) key_label = QLabel(_(''), self) key_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(key_label) name_group = QHBoxLayout() data_group_box_layout.addLayout(name_group) name_group.addWidget(QLabel(u"Your Name:", self)) self.name_ledit = QLineEdit(u"", self) self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " + u"account or on your credit card.</p>" + u"<p>It will only be used to generate this " + u"one-time key and won\'t be stored anywhere " + u"in calibre or on your computer.</p>" + u"<p>(ex: Jonathan Smith)")) name_group.addWidget(self.name_ledit) name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(name_disclaimer_label) ccn_group = QHBoxLayout() data_group_box_layout.addLayout(ccn_group) ccn_group.addWidget(QLabel(u"Credit Card#:", self)) self.cc_ledit = QLineEdit(u"", self) self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " + u"in your B&N account.</p>" + u"<p>No spaces or dashes... just the numbers. " + u"This number will only be used to generate this " + u"one-time key and won\'t be stored anywhere in " + u"calibre or on your computer.")) ccn_group.addWidget(self.cc_ledit) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(ccn_disclaimer_label) layout.addSpacing(10) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key return generate_bandn_key(self.user_name,self.cc_number) @property def user_name(self): return unicode(self.name_ledit.text()).strip().lower().replace(' ','') @property def cc_number(self): return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','') def accept(self): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): errmsg = u"All fields are required!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if not self.cc_number.isdigit(): errmsg = u"Numbers only in the credit card number field!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class ExtendedGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super(ExtendedGroupBox, self).__init__(parent, device, _("Extended driver")) self.options_layout = QGridLayout() self.options_layout.setObjectName("options_layout") self.setLayout(self.options_layout) self.extra_features_checkbox = create_checkbox( _('Enable Extended Kobo Features'), _('Choose whether to enable extra customizations'), device.get_pref('extra_features')) self.upload_encumbered_checkbox = create_checkbox( _('Upload DRM-encumbered ePub files'), _('Select this to upload ePub files encumbered by DRM. If this is ' 'not selected, it is a fatal error to upload an encumbered file' ), device.get_pref('upload_encumbered')) self.skip_failed_checkbox = create_checkbox( _('Silently Ignore Failed Conversions'), _('Select this to not upload any book that fails conversion to ' 'kepub. If this is not selected, the upload process will be ' 'stopped at the first book that fails. If this is selected, ' 'failed books will be silently removed from the upload queue.'), device.get_pref('skip_failed')) self.hyphenate_checkbox = create_checkbox( _('Hyphenate Files'), _('Select this to add a CSS file which enables hyphenation. The ' 'language used will be the language defined for the book in ' 'calibre. Please see the README file for directions on updating ' 'hyphenation dictionaries.'), device.get_pref('hyphenate')) self.replace_lang_checkbox = create_checkbox( _('Replace Content Language Code'), _('Select this to replace the defined language in each content ' 'file inside the ePub.'), device.get_pref('replace_lang')) self.smarten_punctuation_checkbox = create_checkbox( _('Smarten Punctuation'), _('Select this to smarten punctuation in the ePub'), device.get_pref('smarten_punctuation')) self.clean_markup_checkbox = create_checkbox( _('Clean up ePub Markup'), _('Select this to clean up the internal ePub markup.'), device.get_pref('clean_markup')) self.file_copy_dir_checkbox = create_checkbox( _('Copy generated KePub files to a directory'), _('Enter an absolute directory path to copy all generated KePub ' 'files into for debugging purposes.'), device.get_pref('file_copy_dir')) self.file_copy_dir_label = QLabel( _('Copy generated KePub files to a directory')) self.file_copy_dir_edit = QLineEdit(self) self.file_copy_dir_edit.setToolTip( _('Enter an absolute directory path to copy all generated KePub ' 'files into for debugging purposes.')) self.file_copy_dir_edit.setText(device.get_pref('file_copy_dir')) self.file_copy_dir_label.setBuddy(self.file_copy_dir_edit) self.full_page_numbers_checkbox = create_checkbox( _('Use full book page numbers'), _('Select this to show page numbers for the whole book, instead of ' 'each chapter. This will also affect regular ePub page number ' 'display!'), device.get_pref('full_page_numbers')) self.disable_hyphenation_checkbox = create_checkbox( _('Disable hyphenation'), _('Select this to disable hyphenation for books.'), device.get_pref('disable_hyphenation')) self.options_layout.addWidget(self.extra_features_checkbox, 0, 0, 1, 1) self.options_layout.addWidget(self.upload_encumbered_checkbox, 0, 1, 1, 1) self.options_layout.addWidget(self.skip_failed_checkbox, 1, 0, 1, 1) self.options_layout.addWidget(self.hyphenate_checkbox, 1, 1, 1, 1) self.options_layout.addWidget(self.replace_lang_checkbox, 2, 0, 1, 1) self.options_layout.addWidget(self.smarten_punctuation_checkbox, 2, 1, 1, 1) self.options_layout.addWidget(self.clean_markup_checkbox, 3, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_label, 4, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_edit, 4, 1, 1, 1) self.options_layout.addWidget(self.full_page_numbers_checkbox, 5, 0, 1, 1) self.options_layout.addWidget(self.disable_hyphenation_checkbox, 5, 1, 1, 1) self.options_layout.setRowStretch(6, 2) @property def extra_features(self): return self.extra_features_checkbox.isChecked() @property def upload_encumbered(self): return self.upload_encumbered_checkbox.isChecked() @property def skip_failed(self): return self.skip_failed_checkbox.isChecked() @property def hyphenate(self): return self.hyphenate_checkbox.isChecked() @property def replace_lang(self): return self.replace_lang_checkbox.isChecked() @property def smarten_punctuation(self): return self.smarten_punctuation_checkbox.isChecked() @property def clean_markup(self): return self.clean_markup_checkbox.isChecked() @property def full_page_numbers(self): return self.full_page_numbers_checkbox.isChecked() @property def disable_hyphenation(self): return self.disable_hyphenation_checkbox.isChecked() @property def file_copy_dir(self): return self.file_copy_dir_edit.text().strip()
class AnnotationsAppearance(SizePersistedDialog): ''' Dialog for managing CSS rules, including Preview window ''' if isosx: FONT = QFont('Monaco', 12) elif iswindows: FONT = QFont('Lucida Console', 9) elif islinux: FONT = QFont('Monospace', 9) FONT.setStyleHint(QFont.TypeWriter) def __init__(self, parent, icon, prefs): self.opts = parent.opts self.parent = parent self.prefs = prefs self.icon = icon super(AnnotationsAppearance, self).__init__(parent, 'appearance_dialog') self.setWindowTitle('Annotations appearance') self.setWindowIcon(icon) self.l = QVBoxLayout(self) self.setLayout(self.l) # Add a label for description #self.description_label = QLabel("Descriptive text here") #self.l.addWidget(self.description_label) # Add a group box, vertical layout for preview window self.preview_gb = QGroupBox(self) self.preview_gb.setTitle("Preview") self.preview_vl = QVBoxLayout(self.preview_gb) self.l.addWidget(self.preview_gb) self.wv = QWebView() self.wv.setHtml('<p></p>') self.wv.setMinimumHeight(100) self.wv.setMaximumHeight(16777215) self.wv.setGeometry(0, 0, 200, 100) self.wv.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.preview_vl.addWidget(self.wv) # Create a group box, horizontal layout for the table self.css_table_gb = QGroupBox(self) self.css_table_gb.setTitle("Annotation elements") self.elements_hl = QHBoxLayout(self.css_table_gb) self.l.addWidget(self.css_table_gb) # Add the group box to the main layout self.elements_table = AnnotationElementsTable(self, 'annotation_elements_tw') self.elements_hl.addWidget(self.elements_table) self.elements_table.initialize() # Options self.options_gb = QGroupBox(self) self.options_gb.setTitle("Options") self.options_gl = QGridLayout(self.options_gb) self.l.addWidget(self.options_gb) current_row = 0 # <hr/> separator # addWidget(widget, row, col, rowspan, colspan) self.hr_checkbox = QCheckBox('Add horizontal rule between annotations') self.hr_checkbox.stateChanged.connect(self.hr_checkbox_changed) self.hr_checkbox.setCheckState( prefs.get('appearance_hr_checkbox', False)) self.options_gl.addWidget(self.hr_checkbox, current_row, 0, 1, 4) current_row += 1 # Timestamp self.timestamp_fmt_label = QLabel("Timestamp format:") self.options_gl.addWidget(self.timestamp_fmt_label, current_row, 0) self.timestamp_fmt_le = QLineEdit( prefs.get('appearance_timestamp_format', default_timestamp), parent=self) self.timestamp_fmt_le.textEdited.connect(self.timestamp_fmt_changed) self.timestamp_fmt_le.setFont(self.FONT) self.timestamp_fmt_le.setObjectName('timestamp_fmt_le') self.timestamp_fmt_le.setToolTip('Format string for timestamp') self.timestamp_fmt_le.setMaximumWidth(16777215) self.timestamp_fmt_le.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.options_gl.addWidget(self.timestamp_fmt_le, current_row, 1) self.timestamp_fmt_reset_tb = QToolButton(self) self.timestamp_fmt_reset_tb.setToolTip("Reset to default") self.timestamp_fmt_reset_tb.setIcon(QIcon(I('trash.png'))) self.timestamp_fmt_reset_tb.clicked.connect(self.reset_timestamp_to_default) self.options_gl.addWidget(self.timestamp_fmt_reset_tb, current_row, 2) self.timestamp_fmt_help_tb = QToolButton(self) self.timestamp_fmt_help_tb.setToolTip("Format string reference") self.timestamp_fmt_help_tb.setIcon(QIcon(I('help.png'))) self.timestamp_fmt_help_tb.clicked.connect(self.show_help) self.options_gl.addWidget(self.timestamp_fmt_help_tb, current_row, 3) # Button box bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.l.addWidget(bb) # Spacer #self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) #self.l.addItem(self.spacerItem) # Sizing #sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) #sizePolicy.setHorizontalStretch(0) #sizePolicy.setVerticalStretch(0) #sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) #self.setSizePolicy(sizePolicy) self.resize_dialog() def hr_checkbox_changed(self, state): self.prefs.set('appearance_hr_checkbox', state) self.prefs.commit() self.elements_table.preview_css() def reset_timestamp_to_default(self): from calibre_plugins.marvin_manager.appearance import default_timestamp self.timestamp_fmt_le.setText(default_timestamp) self.timestamp_fmt_changed() def show_help(self): ''' Display strftime help file ''' from calibre.gui2 import open_url path = os.path.join(self.parent.resources_path, 'help/timestamp_formats.html') open_url(QUrl.fromLocalFile(path)) def sizeHint(self): return QtCore.QSize(600, 200) def timestamp_fmt_changed(self): self.prefs.set('appearance_timestamp_format', str(self.timestamp_fmt_le.text())) self.prefs.commit() self.elements_table.preview_css()
class AddBandNKeyDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" + u"<p>It should be something that will help you remember " + u"what personal information was used to create it.")) key_group.addWidget(self.key_ledit) name_group = QHBoxLayout() data_group_box_layout.addLayout(name_group) name_group.addWidget(QLabel(u"B&N/nook account email address:", self)) self.name_ledit = QLineEdit(u"", self) self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " + u"account.</p>" + u"<p>It will only be used to generate this " + u"key and won\'t be stored anywhere " + u"in calibre or on your computer.</p>" + u"<p>eg: [email protected]</p>")) name_group.addWidget(self.name_ledit) name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(name_disclaimer_label) ccn_group = QHBoxLayout() data_group_box_layout.addLayout(ccn_group) ccn_group.addWidget(QLabel(u"B&N/nook account password:"******"", self) self.cc_ledit.setToolTip(_(u"<p>Enter the password " + u"for your B&N account.</p>" + u"<p>The password will only be used to generate this " + u"key and won\'t be stored anywhere in " + u"calibre or on your computer.")) ccn_group.addWidget(self.cc_ledit) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(ccn_disclaimer_label) layout.addSpacing(10) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key return fetch_bandn_key(self.user_name,self.cc_number) @property def user_name(self): return unicode(self.name_ledit.text()).strip().lower().replace(' ','') @property def cc_number(self): return unicode(self.cc_ledit.text()).strip() def accept(self): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): errmsg = u"All fields are required!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class AddKindleDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Getting Default Kindle for Mac/PC Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) try: if iswindows or isosx: from calibre_plugins.dedrm.kindlekey import kindlekeys defaultkeys = kindlekeys() else: # linux from wineutils import WineGetKeys scriptpath = os.path.join(parent.parent.alfdir,u"kindlekey.py") defaultkeys = WineGetKeys(scriptpath, u".k4i",parent.getwineprefix()) self.default_key = defaultkeys[0] except: traceback.print_exc() self.default_key = u"" self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) if len(self.default_key)>0: data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit(u"default_key", self) self.key_ledit.setToolTip(u"<p>Enter an identifying name for the current default Kindle for Mac/PC key.") key_group.addWidget(self.key_ledit) self.button_box.accepted.connect(self.accept) else: default_key_error = QLabel(u"The default encryption key for Kindle for Mac/PC could not be found.", self) default_key_error.setAlignment(Qt.AlignHCenter) layout.addWidget(default_key_error) # if no default, both buttons do the same self.button_box.accepted.connect(self.reject) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): return self.default_key def accept(self): if len(self.key_name) == 0 or self.key_name.isspace(): errmsg = u"All fields are required!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class AddBandNKeyDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Create New Barnes & Noble Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(_(u"<p>Enter an identifying name for this new key.</p>" + u"<p>It should be something that will help you remember " + u"what personal information was used to create it.")) key_group.addWidget(self.key_ledit) name_group = QHBoxLayout() data_group_box_layout.addLayout(name_group) name_group.addWidget(QLabel(u"B&N/nook account email address:", self)) self.name_ledit = QLineEdit(u"", self) self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " + u"account.</p>" + u"<p>It will only be used to generate this " + u"key and won\'t be stored anywhere " + u"in calibre or on your computer.</p>" + u"<p>eg: [email protected]</p>")) name_group.addWidget(self.name_ledit) name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(name_disclaimer_label) ccn_group = QHBoxLayout() data_group_box_layout.addLayout(ccn_group) ccn_group.addWidget(QLabel(u"B&N/nook account password:"******"", self) self.cc_ledit.setToolTip(_(u"<p>Enter the password " + u"for your B&N account.</p>" + u"<p>The password will only be used to generate this " + u"key and won\'t be stored anywhere in " + u"calibre or on your computer.")) ccn_group.addWidget(self.cc_ledit) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(ccn_disclaimer_label) layout.addSpacing(10) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Retrieved key:", self)) self.key_display = QLabel(u"", self) self.key_display.setToolTip(_(u"Click the Retrieve Key button to fetch your B&N encryption key from the B&N servers")) key_group.addWidget(self.key_display) self.retrieve_button = QtGui.QPushButton(self) self.retrieve_button.setToolTip(_(u"Click to retrieve your B&N encryption key from the B&N servers")) self.retrieve_button.setText(u"Retrieve Key") self.retrieve_button.clicked.connect(self.retrieve_key) key_group.addWidget(self.retrieve_button) layout.addSpacing(10) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): return unicode(self.key_display.text()).strip() @property def user_name(self): return unicode(self.name_ledit.text()).strip().lower().replace(' ','') @property def cc_number(self): return unicode(self.cc_ledit.text()).strip() def retrieve_key(self): from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key fetched_key = fetch_bandn_key(self.user_name,self.cc_number) if fetched_key == "": errmsg = u"Could not retrieve key. Check username, password and intenet connectivity and try again." error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) else: self.key_display.setText(fetched_key) def accept(self): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): errmsg = u"All fields are required!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_value) == 0: self.retrieve_key() if len(self.key_value) == 0: return QDialog.accept(self)
class CustomColumnsTab(QWidget): def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) custom_columns = self.plugin_action.gui.library_view.model().custom_columns self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_("Save Source column:")) label.setToolTip(_("If set, the column below will be populated with the template below to record the source of the split file.")) label.setWordWrap(True) self.l.addWidget(label) horz = QHBoxLayout() self.sourcecol = QComboBox(self) self.sourcecol.setToolTip(_("Choose a column to populate with template on split.")) self.sourcecol.addItem('','none') for key, column in custom_columns.iteritems(): if column['datatype'] in ('text','comments','series'): self.sourcecol.addItem(column['name'],key) self.sourcecol.setCurrentIndex(self.sourcecol.findData(prefs['sourcecol'])) horz.addWidget(self.sourcecol) self.sourcetemplate = QLineEdit(self) self.sourcetemplate.setToolTip(_("Template from source book. Example: {title} by {authors}")) # if 'sourcetemplate' in prefs: self.sourcetemplate.setText(prefs['sourcetemplate']) # else: # self.sourcetemplate.setText("{title} by {authors}") horz.addWidget(self.sourcetemplate) self.l.addLayout(horz) self.l.addSpacing(5) label = QLabel(_("If you have custom columns defined, they will be listed below. Choose if you would like these columns copied to new split books.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) scrollable = QScrollArea() scrollcontent = QWidget() scrollable.setWidget(scrollcontent) scrollable.setWidgetResizable(True) self.l.addWidget(scrollable) self.sl = QVBoxLayout() scrollcontent.setLayout(self.sl) self.custcol_checkboxes = {} for key, column in custom_columns.iteritems(): # print("\n============== %s ===========\n"%key) # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) checkbox = QCheckBox('%s(%s)'%(column['name'],key)) checkbox.setToolTip(_("Copy this %s column to new split books...")%column['datatype']) checkbox.setChecked(key in prefs['custom_cols'] and prefs['custom_cols'][key]) self.custcol_checkboxes[key] = checkbox self.sl.addWidget(checkbox) self.sl.insertStretch(-1)
class AnnotationsAppearance(SizePersistedDialog): ''' Dialog for managing CSS rules, including Preview window ''' if isosx: FONT = QFont('Monaco', 12) elif iswindows: FONT = QFont('Lucida Console', 9) elif islinux: FONT = QFont('Monospace', 9) FONT.setStyleHint(QFont.TypeWriter) def __init__(self, parent, icon, prefs): self.parent = parent self.prefs = prefs self.icon = icon super(AnnotationsAppearance, self).__init__(parent, 'appearance_dialog') self.setWindowTitle(_('Modify appearance')) self.setWindowIcon(icon) self.l = QVBoxLayout(self) self.setLayout(self.l) # Add a label for description #self.description_label = QLabel(_("Descriptive text here")) #self.l.addWidget(self.description_label) # Add a group box, vertical layout for preview window self.preview_gb = QGroupBox(self) self.preview_gb.setTitle(_("Preview")) self.preview_vl = QVBoxLayout(self.preview_gb) self.l.addWidget(self.preview_gb) self.wv = QWebView() self.wv.setHtml('<p></p>') self.wv.setMinimumHeight(100) self.wv.setMaximumHeight(16777215) self.wv.setGeometry(0, 0, 200, 100) self.wv.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.preview_vl.addWidget(self.wv) # Create a group box, horizontal layout for the table self.css_table_gb = QGroupBox(self) self.css_table_gb.setTitle(_("Annotation elements")) self.elements_hl = QHBoxLayout(self.css_table_gb) self.l.addWidget(self.css_table_gb) # Add the group box to the main layout self.elements_table = AnnotationElementsTable( self, 'annotation_elements_tw') self.elements_hl.addWidget(self.elements_table) self.elements_table.initialize() # Options self.options_gb = QGroupBox(self) self.options_gb.setTitle(_("Options")) self.options_gl = QGridLayout(self.options_gb) self.l.addWidget(self.options_gb) current_row = 0 # <hr/> separator # addWidget(widget, row, col, rowspan, colspan) self.hr_checkbox = QCheckBox( _('Add horizontal rule between annotations')) self.hr_checkbox.stateChanged.connect(self.hr_checkbox_changed) self.hr_checkbox.setCheckState( JSONConfig('plugins/annotations').get('appearance_hr_checkbox', False)) self.options_gl.addWidget(self.hr_checkbox, current_row, 0, 1, 4) current_row += 1 # Timestamp self.timestamp_fmt_label = QLabel(_("Timestamp format:")) self.options_gl.addWidget(self.timestamp_fmt_label, current_row, 0) self.timestamp_fmt_le = QLineEdit( JSONConfig('plugins/annotations').get( 'appearance_timestamp_format', default_timestamp), parent=self) self.timestamp_fmt_le.textEdited.connect(self.timestamp_fmt_changed) self.timestamp_fmt_le.setFont(self.FONT) self.timestamp_fmt_le.setObjectName('timestamp_fmt_le') self.timestamp_fmt_le.setToolTip(_('Format string for timestamp')) self.timestamp_fmt_le.setMaximumWidth(16777215) self.timestamp_fmt_le.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.options_gl.addWidget(self.timestamp_fmt_le, current_row, 1) self.timestamp_fmt_reset_tb = QToolButton(self) self.timestamp_fmt_reset_tb.setToolTip(_("Reset to default")) self.timestamp_fmt_reset_tb.setIcon(QIcon(I('trash.png'))) self.timestamp_fmt_reset_tb.clicked.connect( self.reset_timestamp_to_default) self.options_gl.addWidget(self.timestamp_fmt_reset_tb, current_row, 2) self.timestamp_fmt_help_tb = QToolButton(self) self.timestamp_fmt_help_tb.setToolTip(_("Format string reference")) self.timestamp_fmt_help_tb.setIcon(QIcon(I('help.png'))) self.timestamp_fmt_help_tb.clicked.connect(self.show_help) self.options_gl.addWidget(self.timestamp_fmt_help_tb, current_row, 3) # Button box bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) self.l.addWidget(bb) # Spacer self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.l.addItem(self.spacerItem) # Sizing sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) self.resize_dialog() def hr_checkbox_changed(self, state): self.prefs.set('appearance_hr_checkbox', state) self.elements_table.preview_css() def reset_timestamp_to_default(self): from calibre_plugins.annotations.appearance import default_timestamp self.timestamp_fmt_le.setText(default_timestamp) self.timestamp_fmt_changed() def show_help(self): ''' Display strftime help file ''' help_html = get_resources('help/timestamp_formats.html') print("1 - %s" % help_html) help_html = help_html.decode('utf-8') print("2 - %s" % help_html) hv = HelpView(self, self.icon, self.prefs, html=help_html, title=_("Timestamp formats")) hv.show() def sizeHint(self): return QtCore.QSize(600, 200) def timestamp_fmt_changed(self): self.prefs.set('appearance_timestamp_format', str(self.timestamp_fmt_le.text())) self.elements_table.preview_css()
class Ui_ConfigureRPCserverDlg(object): def setupUi(self, ConfigureRPCserverDlg): ConfigureRPCserverDlg.setModal(True) ## -- Layout self.layout = QGroupBox(ConfigureRPCserverDlg) self.layout.setTitle("Local Pivx-Cli wallet Configuration") self.layout.setContentsMargins(80, 30, 10, 10) form = QFormLayout(ConfigureRPCserverDlg) form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) ## -- ROW 1 line1 = QHBoxLayout() self.edt_rpcIp = QLineEdit() self.edt_rpcIp.setToolTip( "rpc server (local wallet) IP address\n-- example [IPv4] 88.172.23.1\n-- example [IPv6] 2001:db8:85a3::8a2e:370:7334" ) self.edt_rpcIp.setText(ConfigureRPCserverDlg.rpc_ip) line1.addWidget(self.edt_rpcIp) line1.addWidget(QLabel("IP Port")) self.edt_rpcPort = QSpinBox() self.edt_rpcPort.setRange(1, 65535) self.edt_rpcPort.setValue(ConfigureRPCserverDlg.rpc_port) self.edt_rpcPort.setFixedWidth(180) line1.addWidget(self.edt_rpcPort) form.addRow(QLabel("IP Address"), line1) ## -- ROW 2 self.edt_rpcUser = QLineEdit() self.edt_rpcUser.setText(ConfigureRPCserverDlg.rpc_user) form.addRow(QLabel("RPC Username"), self.edt_rpcUser) ## -- ROW 3 self.edt_rpcPassword = QLineEdit() self.edt_rpcPassword.setText(ConfigureRPCserverDlg.rpc_password) form.addRow(QLabel("RPC Password"), self.edt_rpcPassword) ## -- ROW 4 hBox = QHBoxLayout() self.buttonCancel = QPushButton("Cancel") self.buttonCancel.clicked.connect( lambda: self.onButtonCancel(ConfigureRPCserverDlg)) hBox.addWidget(self.buttonCancel) self.buttonSave = QPushButton("Save") self.buttonSave.clicked.connect( lambda: self.onButtonSave(ConfigureRPCserverDlg)) hBox.addWidget(self.buttonSave) form.addRow(hBox) ## Set Layout self.layout.setLayout(form) ConfigureRPCserverDlg.setFixedSize(self.layout.sizeHint()) @pyqtSlot() def onButtonSave(self, main_dlg): main_dlg.rpc_ip = ip_address(self.edt_rpcIp.text().strip()).compressed main_dlg.rpc_port = int(self.edt_rpcPort.value()) main_dlg.rpc_user = self.edt_rpcUser.text() main_dlg.rpc_password = self.edt_rpcPassword.text() conf = {} conf["rpc_ip"] = main_dlg.rpc_ip conf["rpc_port"] = main_dlg.rpc_port conf["rpc_user"] = main_dlg.rpc_user conf["rpc_password"] = main_dlg.rpc_password urlstring = "http://%s:%s@%s:%d" % (conf["rpc_user"], conf["rpc_password"], conf["rpc_ip"], conf["rpc_port"]) if checkRPCstring(urlstring, action_msg="Restoring previous configuration"): # Update datafile writeToFile(conf, rpc_File) # Update current RPC Server main_dlg.main_wnd.mainWindow.rpcClient = None main_dlg.main_wnd.mainWindow.rpcConnected = False printDbg("Trying to connect to RPC server [%s]:%s" % (conf["rpc_ip"], str(conf["rpc_port"]))) self.runInThread = ThreadFuns.runInThread( main_dlg.main_wnd.mainWindow.updateRPCstatus, (), main_dlg.main_wnd.mainWindow.updateRPCled) else: printDbg("Restored RPC credentials. ") main_dlg.close() @pyqtSlot() def onButtonCancel(self, main_wnd): main_wnd.close()
class FunctionPlot(QWidget): '''Plotting of Z(t) = f(X(t),Y(t), where X(t) and Y(t) are the coordinates of the target, and Z(t) is a Python expression given by the user. For example : Z(t) = np.atan2(Y(), X(t), to compute the angle of the polar coordinates of the target. ''' def __init__(self, mainWindow): # call the constructor of the base class: super().__init__(mainWindow) self.mw = mainWindow # remember the reference on thje main window # Attributes (persistent data in the object) self.__figure = None # the plotting figure self.__axe1 = None # plot Z1 Axes self.__axe2 = None # plot Z2 Axes (twinx) self.__canvas = None # used by matplotlib to plot data self.__toolbar = None # the tool bar below the plot self.__time = None # abcissa values for plot self.__xlim = None # plot xmin and xmax self.__z1lim = None # plot ymin and ymax self.__z2lim = None # plot ymin and ymax self.__XYLabel1 = ["",""] # X and Y plot labels self.__XYLabel2 = ["",""] # X and Y plot labels self.__Z1 = None # data to plot: Z1(t) = f(X(t),Y(t),t) self.__Z1 = None # data to plot: Z2(t) = f(X(t),Y(t),t) self.__labelZ1Edit = QLabel("Z1 : Python expr. to plot", self) self.__lineZ1Edit = QLineEdit("Y-Y.mean()",self) self.__labelZ1label = QLabel("Z1 axis label:", self) self.__lineZ1label = QLineEdit("Z1 [unit]",self) self.__labelZ2Edit = QLabel("Z2 : Python expr. to plot", self) self.__lineZ2Edit = QLineEdit("cos(2*pi*12*T)",self) self.__labelZ2label = QLabel("Z2 axis label:", self) self.__lineZ2label = QLineEdit("Z2 [unit]",self) self.__btnPlot1 = QPushButton("Plot",self) self.__btnPlot2 = QPushButton("Plot",self) self.__btnClear1 = QPushButton("Clear",self) self.__btnClear2 = QPushButton("Clear",self) self.__btnClear = QPushButton("Clear All",self) self.__btnCSV = QPushButton(QIcon("icones/csv.png"), "Export CSV", self) self.__initUI() # GUI initialization def __initUI(self): self.__figure, self.__axe1 = plt.subplots() self.__axe2 = self.__axe1.twinx() self.__figure.subplots_adjust(left=0.1,right=0.9,bottom=0.14,top=0.92) self.__canvas = FigureCanvas(self.__figure) self.__toolbar = NavigationToolbar(self.__canvas, self) self.__btnPlot1.clicked.connect(self.PlotZ1) self.__btnPlot2.clicked.connect(self.PlotZ2) self.__btnClear1.clicked.connect(self.ClearAxe1) self.__btnClear2.clicked.connect(self.ClearAxe2) self.__btnClear.clicked.connect(self.ClearAxes) self.__btnCSV.clicked.connect(self.ExportCSV) self.__btnCSV.setEnabled(False) mess = '''Type in a Python expression like: Y-Y.mean() In this expression you can use use: - the position vector X or Y, - the vecolity vector VX or VX, - the time vector T''' self.__lineZ1Edit.setToolTip(mess) self.__lineZ2Edit.setToolTip(mess) self.__lineZ1Edit.setFixedWidth(200) self.__lineZ2Edit.setFixedWidth(200) vbox = QVBoxLayout() self.setLayout(vbox) # Ligne 1 : tracé de l'image self.setLayout(vbox) hbox = QHBoxLayout() hbox.addWidget(self.__labelZ1Edit) hbox.addWidget(self.__lineZ1Edit) hbox.addWidget(self.__labelZ1label) hbox.addWidget(self.__lineZ1label) hbox.addStretch() hbox.addWidget(self.__btnClear1) hbox.addWidget(self.__btnPlot1) vbox.addLayout(hbox) hbox = QHBoxLayout() hbox.addWidget(self.__labelZ2Edit) hbox.addWidget(self.__lineZ2Edit) hbox.addWidget(self.__labelZ2label) hbox.addWidget(self.__lineZ2label) hbox.addStretch() hbox.addWidget(self.__btnClear2) hbox.addWidget(self.__btnPlot2) vbox.addLayout(hbox) vbox.addWidget(self.__canvas) hbox = QHBoxLayout() hbox.addWidget(self.__toolbar) hbox.addStretch() hbox.addWidget(self.__btnClear) hbox.addWidget(self.__btnCSV) vbox.addLayout(hbox) def __AutoSizePlotXZLim(self, num_axe, color): if self.mw.target_pos is None: return if num_axe == 1: X, Z = self.__time, self.__Z1 axe = self.__axe1 xmil, zlim = self.__xlim, self.__z1lim XYLabel = self.__XYLabel1 elif num_axe == 2: X, Z = self.__time, self.__Z2 axe = self.__axe2 xmil, zlim = self.__xlim, self.__z2lim XYLabel = self.__XYLabel2 deltaZ = np.nanmax(Z) - np.nanmin(Z) xlim = np.array([np.nanmin(X), np.nanmax(X)]) zlim = np.array([np.nanmin(Z)-0.1*deltaZ, np.nanmax(Z)+0.1*deltaZ]) axe.set_xlim(*xlim) axe.set_ylim(*zlim) axe.set_xlabel(XYLabel[0]) axe.set_ylabel(XYLabel[1], color=color) axe.set_aspect("auto") self.__canvas.draw() def ClearAxe1(self): self.__axe1.clear() self.__canvas.draw() self.__Z1 = None def ClearAxe2(self): self.__axe2.clear() self.__canvas.draw() self.__Z2 = None def ClearAxes(self): self.__axe1.clear() self.__axe2.clear() self.__canvas.draw() self.__btnCSV.setEnabled(False) def __buildTimeVector(self, target_pos): # Récupération de la valeur de FP (Frame per seconde) pour calcul # du pas de temps et des abscisses : X = target_pos[0] if self.mw.imageTab.video_FPS is not None: deltaT = 1./self.mw.imageTab.video_FPS self.__time = np.arange(len(X))*deltaT self.__XYLabel1[0] = "temps [s]" self.__XYLabel2[0] = "temps [s]" else: self.__time = np.arange(len(X))+1 self.__XYLabel1[0] = "image #" self.__XYLabel2[0] = "image #" def PlotZ1(self): target_pos = self.mw.target_pos if target_pos is None: return self.__buildTimeVector(target_pos) X, Y, T = target_pos[0], target_pos[1], self.__time VX, VY = self.mw.target_veloc expr = self.__lineZ1Edit.text() try: self.__Z1 = eval(expr) except: print("cannot plot this expression <{}>".format(expr)) return self.__XYLabel1[1] = self.__lineZ1label.text() self.__AutoSizePlotXZLim(1, 'b') # tracé de courbe X(t) self.__axe1.plot(self.__time, self.__Z1, color = 'b', marker = 'o', markersize = 2, linewidth = .4, label="Z1(t)="+expr) self.__axe1.grid(True) self.__axe1.legend(fontsize=9, framealpha=0.7, bbox_to_anchor=(-0.1, 1.1), loc='upper left') self.__axe1.tick_params(axis='y', labelcolor='b') self.__canvas.draw() self.__btnCSV.setEnabled(True) def PlotZ2(self): target_pos = self.mw.target_pos if target_pos is None: return self.__buildTimeVector(target_pos) X, Y, T = target_pos[0], target_pos[1], self.__time VX, VY = self.mw.target_veloc expr = self.__lineZ2Edit.text() try: self.__Z2 = eval(expr) except: print("cannot plot this expression <{}>".format(expr)) return self.__XYLabel2[1] = self.__lineZ2label.text() self.__AutoSizePlotXZLim(2, 'm') # tracé de courbe X(t) self.__axe2.plot(self.__time, self.__Z2, color = 'm', marker = 'o', markersize = 2, linewidth = .4, label="Z2(t)="+expr) #self.__axe2.grid(True) self.__axe2.legend(fontsize=8, framealpha=0.7, bbox_to_anchor=(1.1, 1.1), loc='upper right') self.__axe2.tick_params(axis='y', labelcolor='m') self.__canvas.draw() self.__btnCSV.setEnabled(True) def ExportCSV(self): '''Export Data in a CSV file.''' if self.__Z1 is None : self.mw.statusBar().showMessage("No data to export") return # Lance un sélecteur de fichier pour choisir le fichier à écrire.''' fname = QFileDialog.getSaveFileName(self, 'Choose a name for the CSV file to write', self.mw.cur_dir, 'CSV file (*.csv *.txt)') if fname[0] == "": return nbData = len(self.__time) if self.mw.imageTab.video_FPS is not None: deltaT = 1./self.mw.imageTab.video_FPS time = np.arange(nbData)*deltaT tlabel, tformat = "T [seconde]", "%10.6e" else: time = range(1, nbImages+1) tlabel, tformat = "image #", "%10d" # building headers: zlabel = self.__lineZlabel.text() zformat = "%10.6e" header = "{};{}".format(tlabel, zlabel) fmt = (tformat, zformat) data = [] data.append(time) data.append(self.__Z1.tolist()) data = np.array(data) fileName = fname[0] if not fileName.endswith(".csv"): fileName += ".csv" np.savetxt(fileName, data.transpose(), delimiter=";", header=header, fmt=fmt)
class ConfigWidget(QWidget): # GUI definition def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.ll = QHBoxLayout() self.l.addLayout(self.ll) self.label_exe = QLabel(_('&Prince executable:')) self.ll.addWidget(self.label_exe) self.exe = QLineEdit(self) self.exe.setText(prefs['prince_exe']) self.exe.setToolTip( _('<qt>Executable for the Prince program (command-line interface)</qt>' )) self.ll.addWidget(self.exe) self.label_exe.setBuddy(self.exe) self.browse = QPushButton(_('&Browse') + '...', self) self.browse.setToolTip( _('<qt>Search the Prince executable in your computer</qt>')) self.browse.clicked.connect(self.select_exe) self.ll.addWidget(self.browse) self.lll = QHBoxLayout() self.l.addLayout(self.lll) self.label_fmts = QLabel(_('Preferred &formats:')) self.lll.addWidget(self.label_fmts) self.fmts = QLineEdit(self) self.fmts.setText(','.join(prefs['formats'])) self.fmts.setToolTip( _('<qt>Comma-separated list of preferred formats to use as source, the first that matches will be used</qt>' )) self.lll.addWidget(self.fmts) self.label_fmts.setBuddy(self.fmts) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip( _('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.l.addWidget(self.add_book) self.show_css = QCheckBox(_('&Show CSS in the Convert dialog')) self.show_css.setToolTip( _('<qt>Show by default the styles in the Convert dialog</qt>')) self.show_css.setChecked(prefs['show_CSS']) self.l.addWidget(self.show_css) self.css_layout = QVBoxLayout() self.llll = QHBoxLayout() self.css_layout.addLayout(self.llll) self.css_list = QComboBox() self.css_list.setToolTip( _('<qt>List of custom styles defined. Select one to edit</qt>')) self.css_list.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.CSS_list = prefs['custom_CSS_list'].copy() self.default_CSS = prefs['default_CSS'] if 'custom_CSS' in prefs: self.CSS_list[_('old')] = prefs['custom_CSS'] self.default_CSS = _('old') if self.default_CSS not in self.CSS_list: self.default_CSS = sorted(self.CSS_list, key=lambda x: x.lower())[0] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_list.currentIndexChanged.connect(self.set_css) self.llll.addWidget(self.css_list) self.css_rename = QPushButton(_('Re&name')) self.css_rename.setToolTip( _('<qt>Rename the current style to the name on the right</qt>')) self.css_rename.clicked.connect(self.rename_css) self.css_rename.setEnabled(False) self.llll.addWidget(self.css_rename) self.css_name = QLineEdit(self) self.css_name.setToolTip( _('<qt>Name for the new or renamed style</qt>')) self.css_name.setText(self.css_list.currentText()) self.css_name.textChanged.connect(self.check_names) self.llll.addWidget(self.css_name) self.css_add = QPushButton(_('A&dd')) self.css_add.setToolTip( _('<qt>Add a new empty style with the name on the left</qt>')) self.css_add.clicked.connect(self.add_css) self.css_add.setEnabled(False) self.llll.addWidget(self.css_add) self.css_remove = QPushButton(_('Re&move')) self.css_remove.setToolTip(_('<qt>Remove the current style</qt>')) self.css_remove.clicked.connect(self.remove_css) self.llll.addWidget(self.css_remove) self.llll_ = QHBoxLayout() self.css_layout.addLayout(self.llll_) self.label_args = QLabel(_('Addi&tional command-line arguments:')) self.llll_.addWidget(self.label_args) self.args = QLineEdit(self) # Make sure custom_CSS_list and custom_args_list have the same keys if 'custom_args_list' in prefs: self.args_list = prefs['custom_args_list'].copy() else: self.args_list = {} for key in self.CSS_list: if not key in self.args_list: self.args_list[key] = '' for key in self.args_list: if not key in self.CSS_list: del self.args_list[key] self.args.setText(self.args_list[unicode(self.css_list.currentText())]) self.args.setToolTip( _('<qt>Additional command-line arguments used in conversions with this style</qt>' )) self.llll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = TextEditWithTooltip(self, expected_geometry=(80, 20)) self.css.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())], 'css') self.css.setToolTip( _('<qt>Custom stylesheet that will be applied, if selected, to all Prince PDF conversions</qt>' )) self.css_layout.addWidget(self.css) self.css_templates = QLabel(_('Book metadata can be used in the stylesheet. Anything between %(s1)s and %(s2)s will be processed as a calibre template. For instance, %(s3)s in the stylesheet will be replaced with the book title in the conversion.') % \ {'s1':'<span style="font-family:monospace ; font-weight:bold">@{@</span>', \ 's2':'<span style="font-family:monospace ; font-weight:bold">@}@</span>', \ 's3':'<span style="font-family:monospace ; font-weight:bold">@{@{title}@}@</span>'}) self.css_templates.setWordWrap(True) self.css_layout.addWidget(self.css_templates) self.css_box = QGroupBox(_('&Custom styles:')) self.css_box.setLayout(self.css_layout) self.l.addWidget(self.css_box) self.lllll = QHBoxLayout() self.lllll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.lllll) self.defaults = QPushButton(_('&Restore defaults')) self.defaults.setToolTip(_('<qt>Restore the default settings</qt>')) self.defaults.clicked.connect(self.restore_defaults) self.lllll.addWidget(self.defaults, alignment=Qt.AlignLeft) self.warning = QLabel(_('<b>Warning</b>: Deletes modified styles')) self.lllll.addWidget(self.warning) self.adjustSize() def select_exe(self): ''' Create a dialog to select the Prince executable ''' dialog = QFileDialog() dialog.setFileMode(QFileDialog.ExistingFile) filename = dialog.getOpenFileName(self, _('Select Prince executable'), '', '') if filename: try: self.exe.setText(filename) except (TypeError): self.exe.setText(filename[0]) def restore_defaults(self): ''' Restore the default settings ''' self.exe.setText(prefs.defaults['prince_exe']) self.fmts.setText(','.join(prefs.defaults['formats']).lower()) self.show_css.setChecked(prefs.defaults['show_CSS']) self.add_book.setChecked(prefs.defaults['add_book']) self.css_list.currentIndexChanged.disconnect() self.css_list.clear() self.CSS_list = prefs.defaults['custom_CSS_list'].copy() self.args_list = prefs.defaults['custom_args_list'].copy() self.default_CSS = prefs.defaults['default_CSS'] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_name.setText(self.default_CSS) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())], 'css') self.args.setText(self.args_list[unicode(self.css_list.currentText())]) self.css_list.currentIndexChanged.connect(self.set_css) def save_settings(self): ''' Save the current settings ''' prefs['prince_exe'] = unicode(self.exe.text()) prefs['formats'] = unicode(self.fmts.text().lower()).split(',') prefs['show_CSS'] = self.show_css.isChecked() prefs['add_book'] = self.add_book.isChecked() self.set_css() prefs['default_CSS'] = self.default_CSS prefs['custom_CSS_list'] = self.CSS_list.copy() prefs['custom_args_list'] = self.args_list.copy() if 'custom_CSS' in prefs: del prefs['custom_CSS'] def set_css(self): ''' Fill the CSS text box with the selected stylesheet, and similarly for command-line arguments ''' self.CSS_list[self.default_CSS] = unicode(self.css.toPlainText()) self.args_list[self.default_CSS] = unicode(self.args.text()) self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS], 'css') self.args.setText(self.args_list[self.default_CSS]) self.css_name.setText(self.css_list.currentText()) def add_css(self): ''' Add a new style ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog( self, _('Cannot add style'), _('A style with the name "%s" is already defined, use a different name.' ) % name, show=True) else: self.CSS_list[name] = '' self.args_list[name] = '' self.css_list.addItem(name, name) self.css_list.setCurrentIndex(self.css_list.findText(name)) self.css_add.setEnabled(False) self.css_rename.setEnabled(False) def remove_css(self): ''' Remove an existing style ''' from calibre.gui2 import error_dialog if (self.css_list.count() > 1): self.css_list.currentIndexChanged.disconnect() self.css_list.removeItem(self.css_list.currentIndex()) del self.CSS_list[self.default_CSS] del self.args_list[self.default_CSS] self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS], 'css') self.args.setText(self.args_list[self.default_CSS]) self.css_list.currentIndexChanged.connect(self.set_css) self.css_name.setText(self.css_list.currentText()) else: error_dialog( self, _('Cannot delete the last style'), _('The last style cannot be removed. You can rename it and/or remove its contents.' ), show=True) def rename_css(self): ''' Rename a style ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog( self, _('Cannot rename style'), _('A style with the name "%s" is already defined, use a different name.' ) % name, show=True) else: self.CSS_list[name] = self.CSS_list.pop(self.default_CSS) self.args_list[name] = self.args_list.pop(self.default_CSS) self.css_list.setItemText(self.css_list.currentIndex(), name) self.default_CSS = name def check_names(self, text): name = unicode(text) if name in self.CSS_list: self.css_add.setEnabled(False) self.css_rename.setEnabled(False) else: self.css_add.setEnabled(True) self.css_rename.setEnabled(True)
class ConfigWidget(QWidget, Logger): # Manually managed controls when saving/restoring EXCLUDED_CONTROLS = [ 'cfg_annotations_destination_comboBox' ] #LOCATION_TEMPLATE = "{cls}:{func}({arg1}) {arg2}" WIZARD_PROFILES = { 'Annotations': { 'label': 'mm_annotations', 'datatype': 'comments', 'display': {}, 'is_multiple': False } } def __init__(self, plugin_action): self.gui = plugin_action.gui self.opts = plugin_action.opts QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) # ~~~~~~~~ Create the runtime options group box ~~~~~~~~ self.cfg_runtime_options_gb = QGroupBox(self) self.cfg_runtime_options_gb.setTitle(_('Runtime options')) self.l.addWidget(self.cfg_runtime_options_gb) self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb) # ~~~~~~~~ Disable caching checkbox ~~~~~~~~ self.cfg_disable_caching_checkbox = QCheckBox(_('Disable caching')) self.cfg_disable_caching_checkbox.setObjectName('cfg_disable_caching_checkbox') self.cfg_disable_caching_checkbox.setToolTip(_('Force reload of reader database')) self.cfg_disable_caching_checkbox.setChecked(False) self.cfg_runtime_options_qvl.addWidget(self.cfg_disable_caching_checkbox) # ~~~~~~~~ plugin logging checkbox ~~~~~~~~ self.cfg_plugin_debug_log_checkbox = QCheckBox(_('Enable debug logging for Annotations plugin')) self.cfg_plugin_debug_log_checkbox.setObjectName('cfg_plugin_debug_log_checkbox') self.cfg_plugin_debug_log_checkbox.setToolTip(_('Print plugin diagnostic messages to console')) self.cfg_plugin_debug_log_checkbox.setChecked(False) self.cfg_runtime_options_qvl.addWidget(self.cfg_plugin_debug_log_checkbox) # ~~~~~~~~ libiMobileDevice logging checkbox ~~~~~~~~ self.cfg_libimobiledevice_debug_log_checkbox = QCheckBox(_('Enable debug logging for libiMobileDevice')) self.cfg_libimobiledevice_debug_log_checkbox.setObjectName('cfg_libimobiledevice_debug_log_checkbox') self.cfg_libimobiledevice_debug_log_checkbox.setToolTip(_('Print libiMobileDevice debug messages to console')) self.cfg_libimobiledevice_debug_log_checkbox.setChecked(False) self.cfg_libimobiledevice_debug_log_checkbox.setEnabled(LIBIMOBILEDEVICE_AVAILABLE) self.cfg_runtime_options_qvl.addWidget(self.cfg_libimobiledevice_debug_log_checkbox) # ~~~~~~~~ Create the Annotations options group box ~~~~~~~~ self.cfg_annotation_options_gb = QGroupBox(self) self.cfg_annotation_options_gb.setTitle(_('Annotation options')) self.l.addWidget(self.cfg_annotation_options_gb) self.cfg_annotation_options_qgl = QGridLayout(self.cfg_annotation_options_gb) current_row = 0 # Add the label/combobox for annotations destination self.cfg_annotations_destination_label = QLabel(_('<b>Add fetched annotations to<b>')) self.cfg_annotations_destination_label.setAlignment(Qt.AlignLeft) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_label, current_row, 0) current_row += 1 self.cfg_annotations_destination_comboBox = QComboBox(self.cfg_annotation_options_gb) self.cfg_annotations_destination_comboBox.setObjectName('cfg_annotations_destination_comboBox') self.cfg_annotations_destination_comboBox.setToolTip(_('Custom field to store annotations')) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_destination_comboBox, current_row, 0) # Populate annotations_field combobox db = self.gui.current_db all_custom_fields = db.custom_field_keys() self.custom_fields = {} for custom_field in all_custom_fields: field_md = db.metadata_for_field(custom_field) if field_md['datatype'] in ['comments']: self.custom_fields[field_md['name']] = {'field': custom_field, 'datatype': field_md['datatype']} all_fields = self.custom_fields.keys() + ['Comments'] for cf in sorted(all_fields): self.cfg_annotations_destination_comboBox.addItem(cf) # Add CC Wizard self.cfg_annotations_wizard = QToolButton() self.cfg_annotations_wizard.setIcon(QIcon(I('wizard.png'))) self.cfg_annotations_wizard.setToolTip(_("Create a custom column to store annotations")) self.cfg_annotations_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations')) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_wizard, current_row, 2) current_row += 1 # ~~~~~~~~ Add a horizontal line ~~~~~~~~ self.cfg_appearance_hl = QFrame(self) self.cfg_appearance_hl.setGeometry(QRect(0, 0, 1, 3)) self.cfg_appearance_hl.setFrameShape(QFrame.HLine) self.cfg_appearance_hl.setFrameShadow(QFrame.Raised) self.cfg_annotation_options_qgl.addWidget(self.cfg_appearance_hl, current_row, 0) current_row += 1 # ~~~~~~~~ Add the Modify… button ~~~~~~~~ self.cfg_annotations_appearance_pushbutton = QPushButton(_("Modify appearance…")) self.cfg_annotations_appearance_pushbutton.clicked.connect(self.configure_appearance) self.cfg_annotation_options_qgl.addWidget(self.cfg_annotations_appearance_pushbutton, current_row, 0) current_row += 1 self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.cfg_annotation_options_qgl.addItem(self.spacerItem, current_row, 0, 1, 1) # ~~~~~~~~ Compilations group box ~~~~~~~~ self.cfg_compilation_options_gb = QGroupBox(self) self.cfg_compilation_options_gb.setTitle(_('Compilations')) self.l.addWidget(self.cfg_compilation_options_gb) self.cfg_compilation_options_qgl = QGridLayout(self.cfg_compilation_options_gb) current_row = 0 # News clippings self.cfg_news_clippings_checkbox = QCheckBox(_('Collect News clippings')) self.cfg_news_clippings_checkbox.setObjectName('cfg_news_clippings_checkbox') self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_checkbox, current_row, 0) self.cfg_news_clippings_lineEdit = QLineEdit() self.cfg_news_clippings_lineEdit.setObjectName('cfg_news_clippings_lineEdit') self.cfg_news_clippings_lineEdit.setToolTip(_('Title for collected news clippings')) self.cfg_compilation_options_qgl.addWidget(self.cfg_news_clippings_lineEdit, current_row, 1) # ~~~~~~~~ End of construction zone ~~~~~~~~ self.resize(self.sizeHint()) # Restore state of controls, populate annotations combobox self.controls = inventory_controls(self, dump_controls=False) restore_state(self) self.populate_annotations() # Hook changes to annotations_destination_combobox # self.connect(self.cfg_annotations_destination_comboBox, # pyqtSignal('currentIndexChanged(const QString &)'), # self.annotations_destination_changed) self.cfg_annotations_destination_comboBox.currentIndexChanged.connect(self.annotations_destination_changed) # Hook changes to diagnostic checkboxes self.cfg_disable_caching_checkbox.stateChanged.connect(self.restart_required) self.cfg_libimobiledevice_debug_log_checkbox.stateChanged.connect(self.restart_required) self.cfg_plugin_debug_log_checkbox.stateChanged.connect(self.restart_required) # Hook changes to News clippings, initialize self.cfg_news_clippings_checkbox.stateChanged.connect(self.news_clippings_toggled) self.news_clippings_toggled(self.cfg_news_clippings_checkbox.checkState()) self.cfg_news_clippings_lineEdit.editingFinished.connect(self.news_clippings_destination_changed) # Launch the annotated_books_scanner field = get_cc_mapping('annotations', 'field', 'Comments') self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field) self.annotated_books_scanner.signal.connect(self.inventory_complete) # self.connect(self.annotated_books_scanner, self.annotated_books_scanner.signal, # self.inventory_complete) QTimer.singleShot(1, self.start_inventory) def annotations_destination_changed(self, qs_new_destination_name): ''' If the destination field changes, move all existing annotations from old to new ''' self._log_location(repr(qs_new_destination_name)) self._log("self.custom_fields: %s" % self.custom_fields) old_destination_field = get_cc_mapping('annotations', 'field', None) if old_destination_field and not (old_destination_field in self.gui.current_db.custom_field_keys() or old_destination_field == 'Comments'): return old_destination_name = get_cc_mapping('annotations', 'combobox', None) self._log("old_destination_field: %s" % old_destination_field) self._log("old_destination_name: %s" % old_destination_name) # Catch initial change from None to Comments - first run only if old_destination_field is None: return # new_destination_name = unicode(qs_new_destination_name) new_destination_name = unicode(self.cfg_annotations_destination_comboBox.currentText()) self._log("new_destination_name: %s" % new_destination_name) if old_destination_name == new_destination_name: self._log_location("old_destination_name = new_destination_name, no changes") return new_destination_field = None if new_destination_name == 'Comments': new_destination_field = 'Comments' else: new_destination_field = self.custom_fields[new_destination_name]['field'] if existing_annotations(self.opts.parent, old_destination_field): command = self.launch_new_destination_dialog(old_destination_name, new_destination_name) if command == 'move': set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, old_destination_field, new_destination_field) elif command == 'change': # Keep the updated destination field, but don't move annotations pass elif command == 'cancel': # Restore previous destination self.cfg_annotations_destination_comboBox.blockSignals(True) old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name) self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index) self.cfg_annotations_destination_comboBox.blockSignals(False) """ # Warn user that change will move existing annotations to new field title = 'Move annotations?' msg = ("<p>Existing annotations will be moved from <b>%s</b> to <b>%s</b>.</p>" % (old_destination_name, new_destination_name) + "<p>New annotations will be added to <b>%s</b>.</p>" % new_destination_name + "<p>Proceed?</p>") d = MessageBox(MessageBox.QUESTION, title, msg, show_copy_button=False) self._log_location("QUESTION: %s" % msg) if d.exec_(): set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, old_destination_field, new_destination_field) else: self.cfg_annotations_destination_comboBox.blockSignals(True) old_index = self.cfg_annotations_destination_comboBox.findText(old_destination_name) self.cfg_annotations_destination_comboBox.setCurrentIndex(old_index) self.cfg_annotations_destination_comboBox.blockSignals(False) """ else: # No existing annotations, just update prefs set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name) def configure_appearance(self): ''' ''' from calibre_plugins.annotations.appearance import default_elements from calibre_plugins.annotations.appearance import default_timestamp appearance_settings = { 'appearance_css': default_elements, 'appearance_hr_checkbox': False, 'appearance_timestamp_format': default_timestamp } # Save, hash the original settings original_settings = {} osh = hashlib.md5() for setting in appearance_settings: original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting]) osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) # Display the appearance dialog aa = AnnotationsAppearance(self, get_icon('images/annotations.png'), plugin_prefs) cancelled = False if aa.exec_(): # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews plugin_prefs.set('appearance_css', aa.elements_table.get_data()) # Generate a new hash nsh = hashlib.md5() for setting in appearance_settings: nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting]))) else: for setting in appearance_settings: plugin_prefs.set(setting, original_settings[setting]) nsh = osh # If there were changes, and there are existing annotations, offer to re-render field = get_cc_mapping('annotations', 'field', None) if osh.digest() != nsh.digest() and existing_annotations(self.opts.parent,field): title = _('Update annotations?') msg = _('<p>Update existing annotations to new appearance settings?</p>') d = MessageBox(MessageBox.QUESTION, title, msg, show_copy_button=False) self._log_location("QUESTION: %s" % msg) if d.exec_(): self._log_location("Updating existing annotations to modified appearance") if self.annotated_books_scanner.isRunning(): self.annotated_books_scanner.wait() move_annotations(self, self.annotated_books_scanner.annotation_map, field, field, window_title=_("Updating appearance")) def inventory_complete(self, msg): self._log_location(msg) def launch_cc_wizard(self, column_type): ''' ''' def _update_combo_box(comboBox, destination, previous): ''' ''' self._log_location() cb = getattr(self, comboBox) cb.blockSignals(True) all_items = [str(cb.itemText(i)) for i in range(cb.count())] if previous and previous in all_items: all_items.remove(previous) all_items.append(destination) cb.clear() cb.addItems(sorted(all_items, key=lambda s: s.lower())) # Select the new destination in the comboBox idx = cb.findText(destination) if idx > -1: cb.setCurrentIndex(idx) # Process the changed destination self.annotations_destination_changed(destination) cb.blockSignals(False) klass = os.path.join(dialog_resources_path, 'cc_wizard.py') if os.path.exists(klass): #self._log("importing CC Wizard dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('cc_wizard') sys.path.remove(dialog_resources_path) dlg = this_dc.CustomColumnWizard(self, column_type, self.WIZARD_PROFILES[column_type], verbose=True) dlg.exec_() if dlg.modified_column: self._log("modified_column: %s" % dlg.modified_column) destination = dlg.modified_column['destination'] label = dlg.modified_column['label'] previous = dlg.modified_column['previous'] source = dlg.modified_column['source'] self._log("destination: %s" % destination) self._log("label: %s" % label) self._log("previous: %s" % previous) self._log("source: %s" % source) if source == "Annotations": # Add/update the new destination so save_settings() can find it if destination in self.custom_fields: self.custom_fields[destination]['field'] = label else: self.custom_fields[destination] = {'field': label} _update_combo_box('cfg_annotations_destination_comboBox', destination, previous) # Save field manually in case user cancels #self.prefs.set('cfg_annotations_destination_comboBox', destination) #self.prefs.set('cfg_annotations_destination_field', label) set_cc_mapping('annotations', field=label, combobox=destination) # Inform user to restart self.restart_required('custom_column') else: self._log("ERROR: Can't import from '%s'" % klass) def launch_new_destination_dialog(self, old, new): ''' Return 'move', 'change' or 'cancel' ''' self._log_location() klass = os.path.join(dialog_resources_path, 'new_destination.py') if os.path.exists(klass): self._log("importing new destination dialog from '%s'" % klass) sys.path.insert(0, dialog_resources_path) this_dc = importlib.import_module('new_destination') sys.path.remove(dialog_resources_path) dlg = this_dc.NewDestinationDialog(self, old, new) dlg.exec_() return dlg.command def news_clippings_destination_changed(self): qs_new_destination_name = self.cfg_news_clippings_lineEdit.text() if not re.match(r'^\S+[A-Za-z0-9 ]+$', qs_new_destination_name): # Complain about News clippings title title = _('Invalid title for News clippings') msg = _("Supply a valid title for News clippings, for example 'My News Clippings'.") d = MessageBox(MessageBox.WARNING, title, msg, show_copy_button=False) self._log_location("WARNING: %s" % msg) d.exec_() def news_clippings_toggled(self, state): if state == Qt.Checked: self.cfg_news_clippings_lineEdit.setEnabled(True) else: self.cfg_news_clippings_lineEdit.setEnabled(False) def populate_annotations(self): ''' Restore annotations combobox ''' self._log_location() target = 'Comments' existing = get_cc_mapping('annotations', 'combobox') if existing: target = existing ci = self.cfg_annotations_destination_comboBox.findText(target) self.cfg_annotations_destination_comboBox.setCurrentIndex(ci) def restart_required(self, state): title = _('Restart required') msg = _('To apply changes, restart calibre.') d = MessageBox(MessageBox.WARNING, title, msg, show_copy_button=False) self._log_location("WARNING: %s" % (msg)) d.exec_() def save_settings(self): save_state(self) # Save the annotation destination field ann_dest = unicode(self.cfg_annotations_destination_comboBox.currentText()) self._log_location("INFO: ann_dest=%s" % (ann_dest)) self._log_location("INFO: self.custom_fields=%s" % (self.custom_fields)) if ann_dest == 'Comments': set_cc_mapping('annotations', field='Comments', combobox='Comments') elif ann_dest: set_cc_mapping('annotations', field=self.custom_fields[ann_dest]['field'], combobox=ann_dest) def start_inventory(self): self.annotated_books_scanner.start()
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool2' : ( (_('is true'), 'is true',), (_('is false'), 'is not true'), ), 'bool' : ( (_('is true'), 'is true',), (_('is not true'), 'is not true'), (_('is false'), 'is false'), (_('is not false'), 'is not false'), (_('is undefined'), 'is undefined'), (_('is defined'), 'is defined'), ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt'), (_('is set'), 'is set'), (_('is not set'), 'is not set') ), 'datetime' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), (_('is more days ago than'), 'older count days'), (_('is fewer days ago than'), 'count_days'), (_('is more days from now than'), 'newer future days'), (_('is fewer days from now than'), 'older future days') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('contains'), 'contains'), (_('does not contain'), 'does not contain'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=lambda key: sort_key(fm[key]['name'])): self.column_box.addItem('{} ({})'.format(fm[key]['name'], key), key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(20) @property def current_col(self): idx = self.column_box.currentIndex() return unicode_type(self.column_box.itemData(idx) or '') @current_col.setter def current_col(self, val): for idx in range(self.column_box.count()): c = unicode_type(self.column_box.itemData(idx) or '') if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) @property def current_action(self): idx = self.action_box.currentIndex() return unicode_type(self.action_box.itemData(idx) or '') @current_action.setter def current_action(self, val): for idx in range(self.action_box.count()): c = unicode_type(self.action_box.itemData(idx) or '') if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) @property def current_val(self): ans = unicode_type(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v):k for k, v in iteritems(lang_map())} ans = rmap.get(lower(ans), ans) return ans @property def condition(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) @condition.setter def condition(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt == 'bool': from calibre.gui2.ui import get_gui if not get_gui().current_db.new_api.pref('bools_are_tristate'): dt = 'bool2' if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': if action == 'count_days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days old the item can be. Zero is today. ' 'Dates in the future always match') elif action == 'older count days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days old the item can be. Zero is today. ' 'Dates in the future never match') elif action == 'older future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days in the future the item can be. ' 'Zero is today. Dates in the past always match') elif action == 'newer future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days in the future the item can be. ' 'Zero is today. Dates in the past never match') else: self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
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.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 LibraryCodesTab(QWidget): def __init__(self, mygui, myguidb, mymainprefs, myparam_dict, myuiexit, mysavedialoggeometry): super(LibraryCodesTab, self).__init__() #----------------------------------------------------- #----------------------------------------------------- self.gui = mygui #----------------------------------------------------- #----------------------------------------------------- self.guidb = myguidb #----------------------------------------------------- #----------------------------------------------------- self.lib_path = self.gui.library_view.model().db.library_path #----------------------------------------------------- #----------------------------------------------------- self.mytabprefs = mymainprefs #----------------------------------------------------- #----------------------------------------------------- self.param_dict = myparam_dict #----------------------------------------------------- #----------------------------------------------------- self.ui_exit = myuiexit #----------------------------------------------------- #----------------------------------------------------- self.save_dialog_geometry = mysavedialoggeometry #----------------------------------------------------- #----------------------------------------------------- font = QFont() font.setBold(False) font.setPointSize(10) #----------------------------------------------------- self.layout_top = QVBoxLayout() self.layout_top.setSpacing(0) self.layout_top.setAlignment(Qt.AlignLeft) self.setLayout(self.layout_top) #----------------------------------------------------- self.scroll_area_frame = QScrollArea() self.scroll_area_frame.setAlignment(Qt.AlignLeft) self.scroll_area_frame.setWidgetResizable(True) self.scroll_area_frame.ensureVisible(400, 400) self.layout_top.addWidget( self.scroll_area_frame ) # the scroll area is now the child of the parent of self.layout_top # NOTE: the self.scroll_area_frame.setWidget(self.scroll_widget) is at the end of the init() AFTER all children have been created and assigned to a layout... #----------------------------------------------------- self.scroll_widget = QWidget() self.layout_top.addWidget( self.scroll_widget ) # causes automatic reparenting of QWidget to the parent of self.layout_top, which is: self . #----------------------------------------------------- self.layout_frame = QVBoxLayout() self.layout_frame.setSpacing(0) self.layout_frame.setAlignment(Qt.AlignLeft) self.scroll_widget.setLayout( self.layout_frame ) # causes automatic reparenting of any widget later added to self.layout_frame to the parent of self.layout_frame, which is: QWidget . #----------------------------------------------------- self.lc_groupbox = QGroupBox('Settings:') self.lc_groupbox.setMaximumWidth(400) self.lc_groupbox.setToolTip( "<p style='white-space:wrap'>The settings that control 'Library Codes'. Using only ISBN or ISSN or Author/Title, Library Codes for selected books will be derived using the Current Settings." ) self.layout_frame.addWidget(self.lc_groupbox) self.lc_layout = QGridLayout() self.lc_groupbox.setLayout(self.lc_layout) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.spacing0 = QLabel() self.layout_frame.addWidget(self.spacing0) self.spacing0.setMaximumHeight(20) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.button_box = QDialogButtonBox() self.button_box.setOrientation(Qt.Horizontal) self.button_box.setCenterButtons(True) self.layout_frame.addWidget(self.button_box) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.push_button_save_only = QPushButton("Save") self.push_button_save_only.clicked.connect(self.save_settings) self.push_button_save_only.setDefault(True) self.push_button_save_only.setFont(font) self.push_button_save_only.setToolTip( "<p style='white-space:wrap'>Save all user settings.") self.button_box.addButton(self.push_button_save_only, 0) self.push_button_exit_only = QPushButton("Exit") self.push_button_exit_only.clicked.connect(self.exit_only) self.push_button_exit_only.setDefault(False) self.push_button_exit_only.setFont(font) self.push_button_exit_only.setToolTip( "<p style='white-space:wrap'>Exit immediately without saving anything." ) self.button_box.addButton(self.push_button_exit_only, 0) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- r = 4 self.ddc_labelname = QLineEdit(self) self.ddc_labelname.setText(self.mytabprefs['DDC']) self.ddc_labelname.setFont(font) self.ddc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for DDC.<br><br>See: https://www.oclc.org/dewey/features/summaries.en.html" ) self.ddc_labelname.setMaximumWidth(100) self.lc_layout.addWidget(self.ddc_labelname, r, 0) self.ddc_activate_checkbox = QCheckBox( "Activate 'Dewey Decimal Code' Classification?") self.ddc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive DDC?") r = r + 1 self.lc_layout.addWidget(self.ddc_activate_checkbox, r, 0) if prefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): self.ddc_activate_checkbox.setChecked(True) else: self.ddc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing1 = QLabel() r = r + 1 self.lc_layout.addWidget(self.spacing1, r, 0) self.spacing1.setMaximumHeight(10) #----------------------------------------------------- self.lcc_labelname = QLineEdit(self) self.lcc_labelname.setText(self.mytabprefs['LCC']) self.lcc_labelname.setFont(font) self.lcc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for LCC.<br><br>See: http://www.loc.gov/catdir/cpso/lcco/ " ) self.lcc_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.lcc_labelname, r, 0) self.lcc_activate_checkbox = QCheckBox( "Activate 'Library of Congress Code' Classification?") self.lcc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive LCC?") r = r + 1 self.lc_layout.addWidget(self.lcc_activate_checkbox, r, 0) if prefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lcc_activate_checkbox.setChecked(True) else: self.lcc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing2 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing2, r, 0) self.spacing2.setMaximumHeight(10) #----------------------------------------------------- self.fast_labelname = QLineEdit(self) self.fast_labelname.setText(self.mytabprefs['FAST']) self.fast_labelname.setFont(font) self.fast_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for FAST Tag Values. " ) self.fast_labelname.setMinimumWidth(100) self.fast_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.fast_labelname, r, 0) self.fast_activate_checkbox = QCheckBox("Activate 'FAST' Tags?") self.fast_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive FAST Tags?\ <br><br>Text. Behaves like Tags. Not Names.<br><br>" ) r = r + 1 self.lc_layout.addWidget(self.fast_activate_checkbox, r, 0) if prefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): self.fast_activate_checkbox.setChecked(True) else: self.fast_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing6 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing6, r, 0) self.spacing6.setMaximumHeight(10) #----------------------------------------------------- self.oclc_labelname = QLineEdit(self) self.oclc_labelname.setText(self.mytabprefs['OCLC']) self.oclc_labelname.setFont(font) self.oclc_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for OCLC-OWI.<br><br>See: #http://classify.oclc.org/classify2/ " ) self.oclc_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.oclc_labelname, r, 0) self.oclc_activate_checkbox = QCheckBox( "Activate 'Online Computer Library Center' Work ID Code?") self.oclc_activate_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to derive OCLC-OWI?") r = r + 1 self.lc_layout.addWidget(self.oclc_activate_checkbox, r, 0) if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): self.oclc_activate_checkbox.setChecked(True) else: self.oclc_activate_checkbox.setChecked(False) #----------------------------------------------------- self.spacing5 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing5, r, 0) self.spacing5.setMaximumHeight(10) #----------------------------------------------------- self.lc_author_details_labelname = QLineEdit(self) self.lc_author_details_labelname.setText( self.mytabprefs['EXTRA_AUTHOR_DETAILS']) self.lc_author_details_labelname.setFont(font) self.lc_author_details_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for 'LC Extra Author Details'.\ <br><br>Text. Behaves like Tags. Not Names.<br><br>" ) self.lc_author_details_labelname.setMaximumWidth(100) r = r + 4 self.lc_layout.addWidget(self.lc_author_details_labelname, r, 0) self.lc_author_details_checkbox = QCheckBox( "Activate 'Library Codes Extra Author Details'?") self.lc_author_details_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to add (never delete or replace) any available Tag-like values to this Custom Column if they are associated with the OCLC-OWI Identifier?" ) r = r + 1 self.lc_layout.addWidget(self.lc_author_details_checkbox, r, 0) if self.mytabprefs['EXTRA_AUTHOR_DETAILS_IS_ACTIVE'] == unicode_type( S_TRUE): self.lc_author_details_checkbox.setChecked(True) else: self.lc_author_details_checkbox.setChecked(False) #----------------------------------------------------- #----------------------------------------------------- self.spacing4 = QLabel() r = r + 1 self.lc_layout.addWidget(self.spacing4, r, 0) self.spacing4.setMaximumHeight(10) #----------------------------------------------------- font.setBold(False) font.setPointSize(7) #----------------------------------------------------- self.push_button_autoadd_custom_columns = QPushButton( "Automatically Add Activated Custom Columns?") self.push_button_autoadd_custom_columns.clicked.connect( self.autoadd_custom_columns) self.push_button_autoadd_custom_columns.setDefault(False) self.push_button_autoadd_custom_columns.setFont(font) self.push_button_autoadd_custom_columns.setToolTip( "<p style='white-space:wrap'>Do you want to automatically add the Custom Columns selected above?<br><br>If you have any issues, please add them manually." ) r = r + 4 self.lc_layout.addWidget(self.push_button_autoadd_custom_columns, r, 0) self.push_button_autoadd_custom_columns.setMaximumWidth(250) #----------------------------------------------------- self.lc_custom_columns_generation_label = QLabel() r = r + 1 self.lc_layout.addWidget(self.lc_custom_columns_generation_label, r, 0) self.lc_custom_columns_generation_label.setText( " ") self.lc_custom_columns_generation_label.setMaximumHeight(10) self.lc_custom_columns_generation_label.setFont(font) self.oclc_identifier_only_checkbox = QCheckBox( "Always Create OCLC-OWI as an 'Identifier' (à la ISBN)?") self.oclc_identifier_only_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want to update Calibre's Identifiers for an Identifier of 'OCLC-OWI',\ regardless of whether you want its own Custom Column updated?\ <br><br>REQUIRED to derive DDC/LCC using Author/Title." ) r = r + 2 self.lc_layout.addWidget(self.oclc_identifier_only_checkbox, r, 0) if prefs['OCLC_IDENTIFIER'] == unicode_type(S_TRUE): self.oclc_identifier_only_checkbox.setChecked(True) else: self.oclc_identifier_only_checkbox.setChecked(False) #----------------------------------------------------- self.spacing3 = QLabel("") r = r + 1 self.lc_layout.addWidget(self.spacing3, r, 0) self.spacing3.setMaximumHeight(10) #----------------------------------------------------- font.setBold(False) font.setPointSize(10) #----------------------------------------------------- self.lc_genre_labelname = QLineEdit(self) self.lc_genre_labelname.setText(self.mytabprefs['GENRE']) self.lc_genre_labelname.setFont(font) self.lc_genre_labelname.setToolTip( "<p style='white-space:wrap'>Custom Column Search/Lookup #name for 'Genre'.\ <br><br>Text. Behaves like Tags.<br><br>" ) self.lc_genre_labelname.setMaximumWidth(100) r = r + 1 self.lc_layout.addWidget(self.lc_genre_labelname, r, 0) self.lc_checkbox_buttongroup = QButtonGroup() self.lc_checkbox_buttongroup.setExclusive(True) self.lc_genre_ddc_checkbox = QCheckBox( "Update 'Genre' using DDC-to-Genre Mappings?") self.lc_genre_ddc_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want LC to update 'Genre' using the DDC-to-Genre mapping in Table _lc_genre_mapping?" ) r = r + 1 self.lc_layout.addWidget(self.lc_genre_ddc_checkbox, r, 0) self.lc_genre_lcc_checkbox = QCheckBox( "Update 'Genre' using LCC-to-Genre Mappings?") self.lc_genre_lcc_checkbox.setToolTip( "<p style='white-space:wrap'>Do you want LC to update 'Genre' using the LCC-to-Genre mapping in Table _lc_genre_mapping?" ) r = r + 1 self.lc_layout.addWidget(self.lc_genre_lcc_checkbox, r, 0) self.lc_genre_inactive_checkbox = QCheckBox( "Do not update 'Genre' at all") self.lc_genre_inactive_checkbox.setToolTip( "<p style='white-space:wrap'>Do no 'Genre' processing at all?") r = r + 1 self.lc_layout.addWidget(self.lc_genre_inactive_checkbox, r, 0) self.lc_checkbox_buttongroup.addButton(self.lc_genre_ddc_checkbox) self.lc_checkbox_buttongroup.addButton(self.lc_genre_lcc_checkbox) self.lc_checkbox_buttongroup.addButton(self.lc_genre_inactive_checkbox) if self.mytabprefs['GENRE_DDC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lc_genre_ddc_checkbox.setChecked(True) elif self.mytabprefs['GENRE_LCC_IS_ACTIVE'] == unicode_type(S_TRUE): self.lc_genre_lcc_checkbox.setChecked(True) elif self.mytabprefs['GENRE_IS_INACTIVE'] == unicode_type(S_TRUE): self.lc_genre_inactive_checkbox.setChecked(True) self.lc_exact_match_checkbox = QCheckBox( "DDC: Require an 'Exact Match', not a 'Best Match'?") self.lc_exact_match_checkbox.setToolTip( "<p style='white-space:wrap'>Check this checkbox if you want an exact DDC match to be required in Table _lc_genre_mapping. Otherwise, a 'best match' will be used via progressive shortening from right to left, but not past any decimal point. If there is no decimal point in a book's DDC, then no progressive shortening will be performed at all." ) r = r + 1 self.lc_layout.addWidget(self.lc_exact_match_checkbox, r, 0) if self.mytabprefs['GENRE_EXACT_MATCH'] == unicode_type(S_TRUE): self.lc_exact_match_checkbox.setChecked(True) self.spin_lcc = QSpinBox(self) self.spin_lcc.setMinimum(1) self.spin_lcc.setMaximum(50) self.spin_lcc.setProperty('value', prefs['GENRE_LCC_MATCH_LENGTH']) self.spin_lcc.setMaximumWidth(250) self.spin_lcc.setSuffix(" LCC: Maximum Length to Match") self.spin_lcc.setToolTip( "<p style='white-space:nowrap'>Maximum number of characters in the LCC that should be used to map to the 'Genre', starting from the left. A maximum of 1 guarantees a (broad) match.\ <br><br>LCCs are structured with either 1 or 2 beginning letters, so 2-character LCCs have special matching logic.\ <br><br>Example: Assume maximum = 2 for a LCC of 'Q1': Q1 would be attempted. If it failed, because the 2nd digit is a number, 'Q' would be attempted.\ <br><br>Example: Assume maximum = 2 for a LCC of 'PN1969.C65': PN would be attempted. If it failed, nothing else would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'PN1969.C65': PN19 would be attempted. If it failed, nothing else would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'Q1': Q1 would be attempted. If it failed, because the 2nd digit is a number, 'Q' would be attempted.\ <br><br>Example: Assume maximum = 4 for a LCC of 'Q389': Q389 would be attempted. If it failed, nothing else would be attempted." ) r = r + 2 self.lc_layout.addWidget(self.spin_lcc, r, 0) #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- #----------------------------------------------------- self.scroll_widget.resize(self.sizeHint()) #----------------------------------------------------- #----------------------------------------------------- self.scroll_area_frame.setWidget( self.scroll_widget ) # now that all widgets have been created and assigned to a layout... #----------------------------------------------------- #----------------------------------------------------- self.scroll_area_frame.resize(self.sizeHint()) #----------------------------------------------------- #----------------------------------------------------- self.resize(self.sizeHint()) #----------------------------------------------------------------------------------------- def validate(self): return True #----------------------------------------------------------------------------------------- def autoadd_custom_columns(self): number_active = self.save_settings() if number_active == 0: return error_dialog( self.gui, _('Automatically Add Custom Columns'), _('No Activated Library Codes Custom Columns Found. Nothing to Add.' ), show=True) self.cli_param_list = self.create_cli_parameters() is_valid, restart_required = self.create_new_lc_custom_columns( self.cli_param_list) if is_valid: if restart_required: self.lc_custom_columns_generation_label.setText( "Addition of Custom Columns Complete. Restart Calibre Now." ) self.repaint() info_dialog( self.gui, 'Automatically Add Custom Columns', 'All Selected Custom Customs Were Added If They Did Not Already Exist. Please Restart Calibre now.' ).show() else: self.lc_custom_columns_generation_label.setText( "Selected Custom Columns Already Exist. Nothing Done.") self.repaint() else: self.lc_custom_columns_generation_label.setText( "Not Completed. Please Restart Calibre, then Add Manually.") self.repaint() msg = "Fatal error experienced in adding new Custom Columns." error_dialog(self.gui, _('Automatically Add Custom Columns'), _(msg), show=True) #----------------------------------------------------------------------------------------- def create_cli_parameters(self): try: del self.cli_param_list except: pass self.cli_param_list = [] temp_list = [] cc_taglike_list = [] cc_fast_name = "" if self.mytabprefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['DDC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical DDC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['LCC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical LCC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['FAST'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) cc_taglike_list.append(cc) cc_fast_name = cc else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical FAST Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): cc = self.mytabprefs['OCLC'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) else: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Illogical OCLC Settings. Please Correct.'), show=True) return self.cli_param_list if self.mytabprefs['EXTRA_AUTHOR_DETAILS_IS_ACTIVE'] == unicode_type( S_TRUE): cc = self.mytabprefs['EXTRA_AUTHOR_DETAILS'] if cc > '#': cc = cc.replace('#', "").strip() cc = unicode_type(cc) temp_list.append(cc) cc_taglike_list.append(cc) else: error_dialog( self.gui, _('Automatically Add Custom Columns'), _('Illogical LC Extra Author Details Settings. Please Correct.' ), show=True) return self.cli_param_list else: pass if len(temp_list) == 0: del temp_list error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Nothing to do. Please Review Settings.'), show=True) return self.cli_param_list cc_to_add_list = [] # for each cc currently set to active, create a parameter...but only if the cc does NOT already exist... my_db, my_cursor, is_valid = self.apsw_connect_to_library() if not is_valid: error_dialog(self.gui, _('Automatically Add Custom Columns'), _('Database Connection Error. Restart Calibre.'), show=True) return self.lc_custom_columns_generation_label.setText( "...Adding Custom Columns...") self.repaint() mysql = "SELECT label,name FROM custom_columns" my_cursor.execute(mysql) tmp_rows = my_cursor.fetchall() if not tmp_rows: for cc in temp_list: cc_to_add_list.append(cc) #END FOR else: if len(tmp_rows) == 0: for cc in temp_list: cc_to_add_list.append(cc) #END FOR else: for cc in temp_list: label_already_exists = False for row in tmp_rows: label, name = row if unicode_type(label) == unicode_type(cc): label_already_exists = True break else: continue #END FOR if not label_already_exists: cc_to_add_list.append(cc) #END FOR del tmp_rows del temp_list if len(cc_to_add_list) == 0: return self.cli_param_list cc_to_add_list.sort() for label in cc_to_add_list: label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = unicode_type(label) label = label.lower() name = label.upper() datatype = 'text' if label in cc_taglike_list: is_multiple = "--is-multiple" if label == cc_fast_name: name = "FAST Tags" else: name = '"LC Extra Author Details"' param = is_multiple + '|||' + label + '|||' + name + '|||' + datatype else: param = label + '|||' + name + '|||' + datatype param = param.replace("[LIBRARY]", self.lib_path) self.cli_param_list.append(param) #END FOR del cc_to_add_list return self.cli_param_list #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- def apsw_connect_to_library(self): my_db = self.gui.library_view.model().db self.lib_path = my_db.library_path self.lib_path = self.lib_path.replace(os.sep, '/') if isbytestring(self.lib_path): self.lib_path = self.lib_path.decode(filesystem_encoding) path = my_db.library_path if isbytestring(path): path = path.decode(filesystem_encoding) path = path.replace(os.sep, '/') path = os.path.join(path, 'metadata.db') path = path.replace(os.sep, '/') if isbytestring(path): path = path.decode(filesystem_encoding) if path.endswith("/"): path = path[0:-1] if path.count("metadata.db") == 0: path = path + "/metadata.db" try: my_db = apsw.Connection(path) is_valid = True except Exception as e: if DEBUG: print("path to metadata.db is: ", path) if DEBUG: print("error: ", as_unicode(e)) is_valid = False return None, None, is_valid my_cursor = my_db.cursor() mysql = "PRAGMA main.busy_timeout = 5000;" #PRAGMA busy_timeout = milliseconds; my_cursor.execute(mysql) return my_db, my_cursor, is_valid #----------------------------------------------------------------------------------------- def exit_only(self): self.save_dialog_geometry() # inherited from SizePersistedDialog self.ui_exit() #----------------------------------------------------------------------------------------- def save_settings(self): self.save_dialog_geometry() # inherited from SizePersistedDialog self.mytabprefs['DDC'] = self.ddc_labelname.text() self.mytabprefs['LCC'] = self.lcc_labelname.text() self.mytabprefs['FAST'] = self.fast_labelname.text() self.mytabprefs['OCLC'] = self.oclc_labelname.text() self.mytabprefs['DDC_IS_ACTIVE'] = unicode_type( self.ddc_activate_checkbox.isChecked()) self.mytabprefs['LCC_IS_ACTIVE'] = unicode_type( self.lcc_activate_checkbox.isChecked()) self.mytabprefs['FAST_IS_ACTIVE'] = unicode_type( self.fast_activate_checkbox.isChecked()) self.mytabprefs['OCLC_IS_ACTIVE'] = unicode_type( self.oclc_activate_checkbox.isChecked()) self.mytabprefs['OCLC_IDENTIFIER'] = unicode_type( self.oclc_identifier_only_checkbox.isChecked()) label = self.mytabprefs['DDC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.ddc_activate_checkbox.setChecked(False) self.mytabprefs['DDC'] = unicode_type(label) label = self.mytabprefs['LCC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.lcc_activate_checkbox.setChecked(False) self.mytabprefs['LCC'] = unicode_type(label) label = self.mytabprefs['FAST'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.fast_activate_checkbox.setChecked(False) self.mytabprefs['FAST'] = unicode_type(label) label = self.mytabprefs['OCLC'] label = unicode_type(label) label = unicodedata.normalize('NFKD', label).encode('ascii', 'ignore') label = label.lower().strip() if not label.startswith("#"): label = "#" + label if label == "#": label = "" self.oclc_activate_checkbox.setChecked(False) self.mytabprefs['OCLC'] = unicode_type(label) if self.mytabprefs['DDC'] == unicode_type( "") and self.mytabprefs['LCC'] == unicode_type( "") and self.mytabprefs['FAST'] == unicode_type( "") and self.mytabprefs['OCLC'] == unicode_type(""): self.mytabprefs['DDC'] = unicode_type("#ddc") self.mytabprefs['LCC'] = unicode_type("#lcc") self.mytabprefs['FAST'] = unicode_type("#fast") self.mytabprefs['OCLC'] = unicode_type("#oclc_owi") else: if self.mytabprefs['DDC'] == unicode_type( "") and self.mytabprefs['LCC'] == unicode_type(""): self.oclc_identifier_only_checkbox.setChecked(False) #--------------------------------------- s = unicode_type(self.lc_genre_labelname.text()) s = s.strip() if s.startswith("#") and len(s) > 1: self.mytabprefs['GENRE'] = unicode_type(s) self.mytabprefs['GENRE_DDC_IS_ACTIVE'] = unicode_type( self.lc_genre_ddc_checkbox.isChecked()) self.mytabprefs['GENRE_LCC_IS_ACTIVE'] = unicode_type( self.lc_genre_lcc_checkbox.isChecked()) self.mytabprefs['GENRE_IS_INACTIVE'] = unicode_type( self.lc_genre_inactive_checkbox.isChecked()) self.mytabprefs['GENRE_EXACT_MATCH'] = unicode_type( self.lc_exact_match_checkbox.isChecked()) self.mytabprefs['GENRE_LCC_MATCH_LENGTH'] = self.spin_lcc.value() else: self.mytabprefs['GENRE'] = unicode_type("#genre") self.lc_genre_labelname.setText(unicode_type("#genre")) self.lc_genre_ddc_checkbox.setChecked(False) self.lc_genre_lcc_checkbox.setChecked(False) self.lc_genre_inactive_checkbox.setChecked(True) self.mytabprefs['GENRE_DDC_IS_ACTIVE'] = unicode_type(S_FALSE) self.mytabprefs['GENRE_LCC_IS_ACTIVE'] = unicode_type(S_FALSE) self.mytabprefs['GENRE_IS_INACTIVE'] = unicode_type(S_TRUE) self.mytabprefs['GENRE_EXACT_MATCH'] = unicode_type(S_TRUE) self.mytabprefs['GENRE_LCC_MATCH_LENGTH'] = 2 self.repaint() sleep(2) #--------------------------------------- #~ for k,v in self.mytabprefs.iteritems(): for k, v in iteritems(self.mytabprefs): v = unicode_type(v) v = v.strip() prefs[k] = v #END FOR prefs #--------------------------------------- self.ddc_labelname.setText(self.mytabprefs['DDC']) self.lcc_labelname.setText(self.mytabprefs['LCC']) self.fast_labelname.setText(self.mytabprefs['FAST']) self.oclc_labelname.setText(self.mytabprefs['OCLC']) self.repaint() sleep(0) #~ for k,v in self.mytabprefs.iteritems(): for k, v in iteritems(self.mytabprefs): self.param_dict[k] = v #END FOR number_active = 0 if self.mytabprefs['DDC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['LCC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['FAST_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 if self.mytabprefs['OCLC_IS_ACTIVE'] == unicode_type(S_TRUE): number_active = number_active + 1 self.ddc_name = self.mytabprefs['DDC'].replace("#", "").strip() self.lcc_name = self.mytabprefs['LCC'].replace("#", "").strip() self.fast_name = self.mytabprefs['FAST'].replace("#", "").strip() self.oclc_name = self.mytabprefs['OCLC'].replace("#", "").strip() if self.oclc_identifier_only_checkbox.isChecked(): self.oclc_identifier_is_desired = True else: self.oclc_identifier_is_desired = False return number_active #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- def create_new_lc_custom_columns(self, execution_param_list): if len(self.cli_param_list) == 0: return True, False # successful since the labels already exist; no restart is required. dbpath = self.lib_path was_successful = True restart_required = True for param in execution_param_list: try: lc_cli_add_custom_column(self.guidb, param, dbpath) except Exception as e: if DEBUG: print("Exception: ", as_unicode(e)) was_successful = False break #END FOR return was_successful, restart_required #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------- #END of library_codes_dialog.py
class AddEReaderDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Create New eReader Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit("", self) self.key_ledit.setToolTip(u"<p>Enter an identifying name for this new key.\nIt should be something that will help you remember what personal information was used to create it.") key_group.addWidget(self.key_ledit) name_group = QHBoxLayout() data_group_box_layout.addLayout(name_group) name_group.addWidget(QLabel(u"Your Name:", self)) self.name_ledit = QLineEdit(u"", self) self.name_ledit.setToolTip(u"Enter the name for this eReader key, usually the name on your credit card.\nIt will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.\n(ex: Mr Jonathan Q Smith)") name_group.addWidget(self.name_ledit) name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self) name_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(name_disclaimer_label) ccn_group = QHBoxLayout() data_group_box_layout.addLayout(ccn_group) ccn_group.addWidget(QLabel(u"Credit Card#:", self)) self.cc_ledit = QLineEdit(u"", self) self.cc_ledit.setToolTip(u"<p>Enter the last 8 digits of credit card number for this eReader key.\nThey will only be used to generate this one-time key and won\'t be stored anywhere in calibre or on your computer.") ccn_group.addWidget(self.cc_ledit) ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self) ccn_disclaimer_label.setAlignment(Qt.AlignHCenter) data_group_box_layout.addWidget(ccn_disclaimer_label) layout.addSpacing(10) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def key_value(self): from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key return generate_ereader_key(self.user_name,self.cc_number).encode('hex') @property def user_name(self): return unicode(self.name_ledit.text()).strip().lower().replace(' ','') @property def cc_number(self): return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','') def accept(self): if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace(): errmsg = u"All fields are required!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if not self.cc_number.isdigit(): errmsg = u"Numbers only in the credit card number field!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class SynthesisQWidget(QWidget): """ Class who manage Synthesis view with Host and Services QWidgets """ def __init__(self, parent=None): super(SynthesisQWidget, self).__init__(parent) # Fields self.host_widget = HostQWidget() self.services_widget = ServicesQWidget() self.line_search = QLineEdit() self.completer = QCompleter() self.hint_widget = QWidget() def initialize_synthesis(self): """ Initialize Synthesis QWidget """ synthesis_layout = QVBoxLayout() synthesis_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(synthesis_layout) # Search widget search_widget = self.get_search_widget() synthesis_layout.addWidget(search_widget) # Host widget self.host_widget.initialize() self.host_widget.setMaximumHeight(self.width() * 0.5) synthesis_layout.addWidget(self.host_widget) # Hint Widget hint_text = _( '<h4>Dahsboard</h4>' '<ul><li>At the top of App, ' 'you have a dashboard that summarizes the number of items per state.</li></ul>' '<h4>Tabs</h4>' '<ul><li><h4>Host Synthesis</h4></li>' 'Tap in the search bar to view a host and its services.' '<li><h4>Problems</h4></li>' 'The "Problems" tab will show you all problems detected in your backend.' '<li><h4>Spy Hosts</h4></li>' 'A "Spy Host" will keep you regularly informed of his condition. ' 'You will also see problems detected for this host, in the "Spy Hosts" panel.</ul>' '<h4>Alignak</h4>' '<ul><li>You can see your backend status and daemons if available, ' 'as well as your profile.</li></ul>' '<h4>Livestate</h4>' '<ul><li>In the livestate, you can see global state of your monitored items.</li></ul>' '<h4>Events</h4>' '<ul><li>Events will show you informative messages your actions inside App.</li></ul>' ) hint_layout = QVBoxLayout(self.hint_widget) hint_label = QLabel(hint_text) hint_label.setObjectName('subtitle') hint_layout.addWidget(hint_label) synthesis_layout.addWidget(self.hint_widget) # Services widget self.services_widget.initialize() synthesis_layout.addWidget(self.services_widget) # Align all widgets to Top synthesis_layout.setAlignment(Qt.AlignTop) def get_search_widget(self): """ Create and return the search QWidget :return: search QWidget :rtype: QWidget """ widget = QWidget() layout = QHBoxLayout() layout.setSpacing(0) widget.setLayout(layout) # Search label search_lbl = QLabel(_('Search Host')) search_lbl.setObjectName('bordertitle') search_lbl.setFixedHeight(25) search_lbl.setToolTip(_('Search Host')) layout.addWidget(search_lbl) # QLineEdit self.line_search.setFixedHeight(search_lbl.height()) layout.addWidget(self.line_search) self.create_line_search([]) return widget def create_line_search(self, hostnames_list): """ Add all hosts to QLineEdit and set QCompleter :param hostnames_list: list of host names :type hostnames_list: list """ # Get QStringListModel model = self.completer.model() if not model: model = QStringListModel() model.setStringList(hostnames_list) # Configure QCompleter from model self.completer.setFilterMode(Qt.MatchContains) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setModel(model) self.completer.popup().setObjectName('popup') # Add completer to QLineEdit self.line_search.setCompleter(self.completer) self.line_search.setPlaceholderText(_('Type a host name to display its data')) self.line_search.setToolTip(_('Type a host name to display its data')) def update_synthesis(self, host, services, not_spied): """ Update Synthesis QWidget with given host and services :param host: host item :type host: alignak_app.items.host.Host :param services: list of services attached to host :type services: list :param not_spied: define if host is spied or not :type not_spied: bool """ self.host_widget.spy_btn.setEnabled(not_spied) if host: logger.info('Display %s in synthesis view', host.name) # Update Qwidgets self.host_widget.update_host(host) self.services_widget.update_widget(services) self.hint_widget.hide() self.host_widget.show() self.services_widget.show() # If the service element does not have the same ID as the host, reset to None if self.services_widget.service_data_widget.service_item: if self.services_widget.service_data_widget.service_item.data['host'] != \ self.host_widget.host_item.item_id: self.services_widget.service_data_widget.service_item = None else: self.host_widget.hide() self.services_widget.hide() self.hint_widget.show()
class AddAndroidDialog(QDialog): def __init__(self, parent=None,): QDialog.__init__(self, parent) self.parent = parent self.setWindowTitle(u"{0} {1}: Add new Kindle for Android Key".format(PLUGIN_NAME, PLUGIN_VERSION)) layout = QVBoxLayout(self) self.setLayout(layout) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) data_group_box = QGroupBox(u"", self) layout.addWidget(data_group_box) data_group_box_layout = QVBoxLayout() data_group_box.setLayout(data_group_box_layout) file_group = QHBoxLayout() data_group_box_layout.addLayout(file_group) add_btn = QPushButton(u"Choose Backup File", self) add_btn.setToolTip(u"Import Kindle for Android backup file.") add_btn.clicked.connect(self.get_android_file) file_group.addWidget(add_btn) self.selected_file_name = QLabel(u"",self) self.selected_file_name.setAlignment(Qt.AlignHCenter) file_group.addWidget(self.selected_file_name) key_group = QHBoxLayout() data_group_box_layout.addLayout(key_group) key_group.addWidget(QLabel(u"Unique Key Name:", self)) self.key_ledit = QLineEdit(u"", self) self.key_ledit.setToolTip(u"<p>Enter an identifying name for the Android for Kindle key.") key_group.addWidget(self.key_ledit) #key_label = QLabel(_(''), self) #key_label.setAlignment(Qt.AlignHCenter) #data_group_box_layout.addWidget(key_label) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.resize(self.sizeHint()) @property def key_name(self): return unicode(self.key_ledit.text()).strip() @property def file_name(self): return unicode(self.selected_file_name.text()).strip() @property def key_value(self): return self.serials_from_file def get_android_file(self): unique_dlg_name = PLUGIN_NAME + u"Import Kindle for Android backup file" #takes care of automatically remembering last directory caption = u"Select Kindle for Android backup file to add" filters = [(u"Kindle for Android backup files", ['db','ab','xml'])] files = choose_files(self, unique_dlg_name, caption, filters, all_files=False) self.serials_from_file = [] file_name = u"" if files: # find the first selected file that yields some serial numbers for filename in files: fpath = os.path.join(config_dir, filename) self.filename = os.path.basename(filename) file_serials = androidkindlekey.get_serials(fpath) if len(file_serials)>0: file_name = os.path.basename(self.filename) self.serials_from_file.extend(file_serials) self.selected_file_name.setText(file_name) def accept(self): if len(self.file_name) == 0 or len(self.key_value) == 0: errmsg = u"Please choose a Kindle for Android backup file." return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) == 0 or self.key_name.isspace(): errmsg = u"Please enter a key name." return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) if len(self.key_name) < 4: errmsg = u"Key name must be at <i>least</i> 4 characters long!" return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False) QDialog.accept(self)
class BasicTab(QWidget): def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_('These settings control the basic features of the plugin.')) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) no_toc_warning = _('''If both 'Insert Table of Contents entry' and 'Copy Table of Contents entries' are unchecked, there will be no Table of Contents in merged books.''') self.titlenavpoints = QCheckBox(_('Insert Table of Contents entry for each title?'),self) self.titlenavpoints.setToolTip(_('''If set, a new TOC entry will be made for each title and it's existing TOC nested underneath it.''')+'\n'+no_toc_warning) self.titlenavpoints.setChecked(prefs['titlenavpoints']) self.l.addWidget(self.titlenavpoints) self.originalnavpoints = QCheckBox(_('Copy Table of Contents entries from each title?'),self) self.originalnavpoints.setToolTip(_('''If set, the original TOC entries will be included the new epub.''')+'\n'+no_toc_warning) self.originalnavpoints.setChecked(prefs['originalnavpoints']) self.l.addWidget(self.originalnavpoints) def f(): if not self.originalnavpoints.isChecked() and not self.titlenavpoints.isChecked(): confirm("<br>"+no_toc_warning, # force HTML to get auto wrap. 'epubmerge_no_toc_warning_again', parent=self, show_cancel_button=False) self.originalnavpoints.stateChanged.connect(f) self.titlenavpoints.stateChanged.connect(f) self.flattentoc = QCheckBox(_('Flatten Table of Contents?'),self) self.flattentoc.setToolTip(_('Remove nesting and make TOC all on one level.')) self.flattentoc.setChecked(prefs['flattentoc']) self.l.addWidget(self.flattentoc) self.includecomments = QCheckBox(_("Include Books' Comments?"),self) self.includecomments.setToolTip(_('''Include all the merged books' comments in the new book's comments. Default is a list of included titles only.''')) self.includecomments.setChecked(prefs['includecomments']) self.l.addWidget(self.includecomments) self.keepmeta = QCheckBox(_('Keep UnMerge Metadata?'),self) self.keepmeta.setToolTip(_('''If set, a copy of the original metadata for each merged book will be included, allowing for UnMerge. This includes your calibre custom columns. Leave off if you plan to distribute the epub to others.''')) self.keepmeta.setChecked(prefs['keepmeta']) self.l.addWidget(self.keepmeta) # self.showunmerge = QCheckBox(_('Show UnMerge Option?'),self) # self.showunmerge.setToolTip(_('''If set, the UnMerge Epub option will be shown on the EpubMerge menu. # Only Epubs merged with 'Keep UnMerge Metadata' can be UnMerged.''')) # self.showunmerge.setChecked(prefs['showunmerge']) # self.l.addWidget(self.showunmerge) horz = QHBoxLayout() horz.addWidget(QLabel(_("Add tags to merged books:"))) self.mergetags = QLineEdit(self) self.mergetags.setText(prefs['mergetags']) self.mergetags.setToolTip(_('Tags you enter here will be added to all new merged books')) horz.addWidget(self.mergetags) self.l.addLayout(horz) horz = QHBoxLayout() horz.addWidget(QLabel(_("Merged Book Word:"))) self.mergeword = QLineEdit(self) self.mergeword.setText(prefs['mergeword']) self.mergeword.setToolTip(_('''Word use to describe merged books in default title and summary. For people who don't like the word Anthology.''')) ## Defaults back to Anthology if cleared. horz.addWidget(self.mergeword) self.l.addLayout(horz) self.l.addSpacing(15) label = QLabel(_("These controls aren't plugin settings as such, but convenience buttons for setting Keyboard shortcuts and getting all the EpubMerge confirmation dialogs back again.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) keyboard_shortcuts_button = QPushButton(_('Keyboard shortcuts...'), self) keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin')) keyboard_shortcuts_button.clicked.connect(parent_dialog.edit_shortcuts) self.l.addWidget(keyboard_shortcuts_button) reset_confirmation_button = QPushButton(_('Reset disabled &confirmation dialogs'), self) reset_confirmation_button.setToolTip(_('Reset all show me again dialogs for the EpubMerge plugin')) reset_confirmation_button.clicked.connect(self.reset_dialogs) self.l.addWidget(reset_confirmation_button) view_prefs_button = QPushButton(_('View library preferences...'), self) view_prefs_button.setToolTip(_('View data stored in the library database for this plugin')) view_prefs_button.clicked.connect(self.view_prefs) self.l.addWidget(view_prefs_button) self.l.insertStretch(-1) def view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubmerge_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
class MakeBrickDialog(QDialog): def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.model = None self._model_dir = None self.setModal(modal) self.setWindowTitle("Convert sources to FITS brick") lo = QVBoxLayout(self) lo.setContentsMargins(10, 10, 10, 10) lo.setSpacing(5) # file selector self.wfile = FileSelector(self, label="FITS filename:", dialog_label="Output FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo.addWidget(self.wfile) # reference frequency lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) label = QLabel("Frequency, MHz:", self) lo1.addWidget(label) tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>""" self.wfreq = QLineEdit(self) self.wfreq.setValidator(QDoubleValidator(self)) label.setToolTip(tip) self.wfreq.setToolTip(tip) lo1.addWidget(self.wfreq) # beam gain lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpb_apply = QCheckBox("Apply primary beam expression:", self) self.wpb_apply.setChecked(True) lo1.addWidget(self.wpb_apply) tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>""" self.wpb_exp = QLineEdit(self) self.wpb_apply.setToolTip(tip) self.wpb_exp.setToolTip(tip) lo1.addWidget(self.wpb_exp) # overwrite or add mode lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.woverwrite = QRadioButton("overwrite image", self) self.woverwrite.setChecked(True) lo1.addWidget(self.woverwrite) self.waddinto = QRadioButton("add into image", self) lo1.addWidget(self.waddinto) # add to model self.wadd = QCheckBox( "Add resulting brick to sky model as a FITS image component", self) lo.addWidget(self.wadd) lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpad = QLineEdit(self) self.wpad.setValidator(QDoubleValidator(self)) self.wpad.setText("1.1") lab = QLabel("...with padding factor:", self) lab.setToolTip( """<P>The padding factor determines the amount of null padding inserted around the image during the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size. This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is perfectly fine.</P>""") self.wpad.setToolTip(lab.toolTip()) self.wadd.toggled[bool].connect(self.wpad.setEnabled) self.wadd.toggled[bool].connect(lab.setEnabled) self.wpad.setEnabled(False) lab.setEnabled(False) lo1.addStretch(1) lo1.addWidget(lab, 0) lo1.addWidget(self.wpad, 1) self.wdel = QCheckBox( "Remove from the sky model sources that go into the brick", self) lo.addWidget(self.wdel) # OK/cancel buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(5, 5, 5, 5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) self.wokbtn.clicked.connect(self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) cancelbtn.clicked.connect(self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) # signals self.wfile.filenameSelected.connect(self._fileSelected) # internal state self.qerrmsg = QErrorMessage(self) def setModel(self, model): self.model = model pb = self.model.primaryBeam() if pb: self.wpb_exp.setText(pb) else: self.wpb_apply.setChecked(False) self.wpb_exp.setText("") if model.filename(): self._model_dir = os.path.dirname(os.path.abspath( model.filename())) else: self._model_dir = os.path.abspath('.') self.wfile.setDirectory(self._model_dir) self._fileSelected(self.wfile.filename(), quiet=True) def _fileSelected(self, filename, quiet=False): self.wokbtn.setEnabled(False) if not filename: return None # check that filename matches model if not os.path.samefile(self._model_dir, os.path.dirname(filename)): self.wfile.setFilename('') if not quiet: QMessageBox.warning( self, "Directory mismatch", """<P>The FITS file must reside in the same directory as the current sky model.</P>""") self.wfile.setDirectory(self._model_dir) return None # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] hdr = input_hdu.header # get frequency, if specified for axis in range(1, hdr['NAXIS'] + 1): if hdr['CTYPE%d' % axis].upper() == 'FREQ': self.wfreq.setText(str(hdr['CRVAL%d' % axis] / 1e+6)) break except Exception as err: busy.reset_cursor() self.wfile.setFilename('') if not quiet: QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return None self.wokbtn.setEnabled(True) # if filename is not in model already, enable the "add to model" control for src in self.model.sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) \ and os.path.exists(src.shape.filename) and os.path.exists(filename) \ and os.path.samefile(src.shape.filename, filename): self.wadd.setChecked(True) self.wadd.setEnabled(False) self.wadd.setText("image already in sky model") break else: self.wadd.setText("add image to sky model") busy.reset_cursor() return filename def accept(self): """Tries to make a brick, and closes the dialog if successful.""" sources = [ src for src in self.model.sources if src.selected and src.typecode == 'pnt' ] filename = self.wfile.filename() if not self._fileSelected(filename): return # get PB expression pbfunc = None if self.wpb_apply.isChecked(): pbexp = str(self.wpb_exp.text()) try: pbfunc = eval("lambda r,fq:" + pbexp) except Exception as err: QMessageBox.warning( self, "Error parsing PB experssion", "Error parsing primary beam expression %s: %s" % (pbexp, str(err))) return # get frequency freq = str(self.wfreq.text()) freq = float(freq) * 1e+6 if freq else None # get pad factor pad = str(self.wpad.text()) pad = max(float(pad), 1) if pad else 1 # read fits file busy = BusyIndicator() try: input_hdu = pyfits.open(filename)[0] except Exception as err: busy.reset_cursor() QMessageBox.warning( self, "Error reading FITS", "Error reading FITS file %s: %s" % (filename, str(err))) return # reset data if asked to if self.woverwrite.isChecked(): input_hdu.data[...] = 0 # insert sources Imaging.restoreSources(input_hdu, sources, 0, primary_beam=pbfunc, freq=freq) # save fits file try: # pyfits seems to produce an exception: # TypeError: formatwarning() takes exactly 4 arguments (5 given) # when attempting to overwrite a file. As a workaround, remove the file first. if os.path.exists(filename): os.remove(filename) input_hdu.writeto(filename) except Exception as err: traceback.print_exc() busy.reset_cursor() QMessageBox.warning( self, "Error writing FITS", "Error writing FITS file %s: %s" % (filename, str(err))) return changed = False sources = self.model.sources # remove sources from model if asked to if self.wdel.isChecked(): sources = [ src for src in sources if not (src.selected and src.typecode == 'pnt') ] changed = True # add image to model if asked to if self.wadd.isChecked(): hdr = input_hdu.header # get image parameters max_flux = float(input_hdu.data.max()) wcs = WCS(hdr, mode='pyfits') # Get reference pixel coordinates # wcs.getCentreWCSCoords() doesn't work, as that gives us the middle of the image # So scan the header to get the CRPIX values ra0 = dec0 = 1 for iaxis in range(hdr['NAXIS']): axs = str(iaxis + 1) name = hdr.get('CTYPE' + axs, axs).upper() if name.startswith("RA"): ra0 = hdr.get('CRPIX' + axs, 1) - 1 elif name.startswith("DEC"): dec0 = hdr.get('CRPIX' + axs, 1) - 1 # convert pixel to degrees ra0, dec0 = wcs.pix2wcs(ra0, dec0) ra0 *= DEG dec0 *= DEG sx, sy = wcs.getHalfSizeDeg() sx *= DEG sy *= DEG nx, ny = input_hdu.data.shape[-1:-3:-1] # check if this image is already contained in the model for src in sources: if isinstance(getattr(src, 'shape', None), ModelClasses.FITSImage) and os.path.samefile( src.shape.filename, filename): # update source parameters src.pos.ra, src.pos.dec = ra0, dec0 src.flux.I = max_flux src.shape.ex, src.shape.ey = sx, sy src.shape.nx, src.shape.ny = nx, ny src.shape.pad = pad break # not contained, make new source object else: pos = ModelClasses.Position(ra0, dec0) flux = ModelClasses.Flux(max_flux) shape = ModelClasses.FITSImage(sx, sy, 0, os.path.basename(filename), nx, ny, pad=pad) img_src = SkyModel.Source(os.path.splitext( os.path.basename(filename))[0], pos, flux, shape=shape) sources.append(img_src) changed = True if changed: self.model.setSources(sources) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self) self.parent().showMessage("Wrote %d sources to FITS file %s" % (len(sources), filename)) busy.reset_cursor() return QDialog.accept(self)
class Synthesis(QWidget): """ Class who create the Synthesis QWidget. """ first_display = True def __init__(self, parent=None): super(Synthesis, self).__init__(parent) self.setStyleSheet(get_css()) # Fields self.line_search = QLineEdit() self.app_backend = None self.action_manager = None self.host_synthesis = None self.app_widget = AppQWidget() self.old_checkbox_states = {} def initialize(self, app_backend): """ Create the QWidget with its items and layout. :param app_backend: app_backend of alignak. :type app_backend: alignak_app.core.backend.AppBackend """ logger.info('Create Synthesis View...') # App_backend self.app_backend = app_backend self.action_manager = ActionManager(app_backend) layout = QGridLayout() self.setLayout(layout) # button button = QPushButton('Search / Refresh Host', self) button.setObjectName('search') button.setFixedHeight(22) button.setToolTip('Search Host') button.clicked.connect(self.display_host_synthesis) layout.addWidget(button, 0, 4, 1, 1) layout.setAlignment(button, Qt.AlignTop) self.line_search.setFixedHeight(button.height()) self.line_search.returnPressed.connect(button.click) self.line_search.cursorPositionChanged.connect(button.click) layout.addWidget(self.line_search, 0, 0, 1, 4) layout.setAlignment(self.line_search, Qt.AlignTop) self.app_widget.initialize('Host Synthesis View') self.app_widget.add_widget(self) self.app_widget.setMinimumSize(1300, 750) refresh_interval = int(get_app_config('Alignak-App', 'item_interval')) if bool(refresh_interval) and refresh_interval > 0: logger.debug('Hosts synthesis will be refresh in %ss', str(refresh_interval)) refresh_interval *= 1000 else: logger.error( '"item_interval" option must be greater than 0. Replace by default: 30s' ) refresh_interval = 30000 refresh_timer = QTimer(self) refresh_timer.start(refresh_interval) refresh_timer.timeout.connect(self.display_host_synthesis) def create_line_search(self): """ Add all hosts to QLineEdit and set QCompleter """ # Create list for QStringModel hosts_list = [] params = {'where': json.dumps({'_is_template': False})} all_hosts = self.app_backend.get('host', params, ['name']) if all_hosts: for host in all_hosts['_items']: hosts_list.append(host['name']) model = QStringListModel() model.setStringList(hosts_list) # Create completer from model completer = QCompleter() completer.setFilterMode(Qt.MatchContains) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setModel(model) # Add completer to "line edit" self.line_search.setCompleter(completer) self.line_search.setPlaceholderText( 'Type a host name to display its data') self.line_search.setToolTip('Type a host name to display its data') def display_host_synthesis(self): """ Display Synthesis QWidget. Remove and delete HostSynthesis if exists """ if self.isVisible() or self.first_display: # If first display, create line search from hosts list if self.first_display: self.create_line_search() self.first_display = False host_name = str(self.line_search.text()).rstrip() backend_data = None old_row = -1 if host_name: backend_data = self.app_backend.get_host_with_services( host_name) # Store old data, remove and delete host_synthesis if self.host_synthesis: # Store old data if self.host_synthesis.services_list: old_row = self.host_synthesis.services_list.currentRow() if self.host_synthesis.check_boxes: for key in self.host_synthesis.check_boxes: self.old_checkbox_states[key] = \ self.host_synthesis.check_boxes[key].isChecked() # Remove and delete QWidget self.layout().removeWidget(self.host_synthesis) self.host_synthesis.deleteLater() self.host_synthesis = None # Create the new host_synthesis self.host_synthesis = HostSynthesis(self.action_manager) self.host_synthesis.initialize(backend_data) # Restore old data if old_row >= 0 and self.host_synthesis.services_list: self.host_synthesis.services_list.setCurrentRow(old_row) if self.old_checkbox_states and self.host_synthesis.check_boxes: for key, checked in self.old_checkbox_states.items(): try: self.host_synthesis.check_boxes[key].setChecked( checked) except KeyError as e: logger.warning('Can\'t reapply filter [%s]: %s', e, checked) self.layout().addWidget(self.host_synthesis, 1, 0, 1, 5)
class ExtendedGroupBox(DeviceOptionsGroupBox): def __init__(self, parent, device): super(ExtendedGroupBox, self).__init__(parent, device, _("Extended driver")) self.options_layout = QGridLayout() self.options_layout.setObjectName("options_layout") self.setLayout(self.options_layout) self.extra_features_checkbox = create_checkbox( _('Enable Extended Kobo Features'), _('Choose whether to enable extra customizations'), device.get_pref('extra_features')) self.upload_encumbered_checkbox = create_checkbox( _('Upload DRM-encumbered ePub files'), _('Select this to upload ePub files encumbered by DRM. If this is ' 'not selected, it is a fatal error to upload an encumbered file'), device.get_pref('upload_encumbered')) self.skip_failed_checkbox = create_checkbox( _('Silently Ignore Failed Conversions'), _('Select this to not upload any book that fails conversion to ' 'kepub. If this is not selected, the upload process will be ' 'stopped at the first book that fails. If this is selected, ' 'failed books will be silently removed from the upload queue.'), device.get_pref('skip_failed')) self.hyphenate_checkbox = create_checkbox( _('Hyphenate Files'), _('Select this to add a CSS file which enables hyphenation. The ' 'language used will be the language defined for the book in ' 'calibre. Please see the README file for directions on updating ' 'hyphenation dictionaries.'), device.get_pref('hyphenate')) self.replace_lang_checkbox = create_checkbox( _('Replace Content Language Code'), _('Select this to replace the defined language in each content ' 'file inside the ePub.'), device.get_pref('replace_lang')) self.smarten_punctuation_checkbox = create_checkbox( _('Smarten Punctuation'), _('Select this to smarten punctuation in the ePub'), device.get_pref('smarten_punctuation')) self.clean_markup_checkbox = create_checkbox( _('Clean up ePub Markup'), _('Select this to clean up the internal ePub markup.'), device.get_pref('clean_markup')) self.file_copy_dir_checkbox = create_checkbox( _('Copy generated KePub files to a directory'), _('Enter an absolute directory path to copy all generated KePub ' 'files into for debugging purposes.'), device.get_pref('file_copy_dir')) self.file_copy_dir_label = QLabel(_( 'Copy generated KePub files to a directory')) self.file_copy_dir_edit = QLineEdit(self) self.file_copy_dir_edit.setToolTip( _('Enter an absolute directory path to copy all generated KePub ' 'files into for debugging purposes.')) self.file_copy_dir_edit.setText(device.get_pref('file_copy_dir')) self.file_copy_dir_label.setBuddy(self.file_copy_dir_edit) self.full_page_numbers_checkbox = create_checkbox( _('Use full book page numbers'), _('Select this to show page numbers for the whole book, instead of ' 'each chapter. This will also affect regular ePub page number ' 'display!'), device.get_pref('full_page_numbers')) self.disable_hyphenation_checkbox = create_checkbox( _('Disable hyphenation'), _('Select this to disable hyphenation for books.'), device.get_pref('disable_hyphenation')) self.options_layout.addWidget(self.extra_features_checkbox, 0, 0, 1, 1) self.options_layout.addWidget(self.upload_encumbered_checkbox, 0, 1, 1, 1) self.options_layout.addWidget(self.skip_failed_checkbox, 1, 0, 1, 1) self.options_layout.addWidget(self.hyphenate_checkbox, 1, 1, 1, 1) self.options_layout.addWidget(self.replace_lang_checkbox, 2, 0, 1, 1) self.options_layout.addWidget(self.smarten_punctuation_checkbox, 2, 1, 1, 1) self.options_layout.addWidget(self.clean_markup_checkbox, 3, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_label, 4, 0, 1, 1) self.options_layout.addWidget(self.file_copy_dir_edit, 4, 1, 1, 1) self.options_layout.addWidget(self.full_page_numbers_checkbox, 5, 0, 1, 1) self.options_layout.addWidget(self.disable_hyphenation_checkbox, 5, 1, 1, 1) self.options_layout.setRowStretch(6, 2) @property def extra_features(self): return self.extra_features_checkbox.isChecked() @property def upload_encumbered(self): return self.upload_encumbered_checkbox.isChecked() @property def skip_failed(self): return self.skip_failed_checkbox.isChecked() @property def hyphenate(self): return self.hyphenate_checkbox.isChecked() @property def replace_lang(self): return self.replace_lang_checkbox.isChecked() @property def smarten_punctuation(self): return self.smarten_punctuation_checkbox.isChecked() @property def clean_markup(self): return self.clean_markup_checkbox.isChecked() @property def full_page_numbers(self): return self.full_page_numbers_checkbox.isChecked() @property def disable_hyphenation(self): return self.disable_hyphenation_checkbox.isChecked() @property def file_copy_dir(self): return self.file_copy_dir_edit.text().strip()
class BasicTab(QWidget): def __init__(self, parent_dialog, plugin_action): self.parent_dialog = parent_dialog self.plugin_action = plugin_action QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) label = QLabel(_('These settings control the basic features of the plugin.')) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) self.titlenavpoints = QCheckBox(_('Insert Table of Contents entry for each title?'),self) self.titlenavpoints.setToolTip(_('''If set, a new TOC entry will be made for each title and it's existing TOC nested underneath it.''')) self.titlenavpoints.setChecked(prefs['titlenavpoints']) self.l.addWidget(self.titlenavpoints) self.flattentoc = QCheckBox(_('Flatten Table of Contents?'),self) self.flattentoc.setToolTip(_('Remove nesting and make TOC all on one level.')) self.flattentoc.setChecked(prefs['flattentoc']) self.l.addWidget(self.flattentoc) self.includecomments = QCheckBox(_("Include Books' Comments?"),self) self.includecomments.setToolTip(_('''Include all the merged books' comments in the new book's comments. Default is a list of included titles only.''')) self.includecomments.setChecked(prefs['includecomments']) self.l.addWidget(self.includecomments) self.keepmeta = QCheckBox(_('Keep UnMerge Metadata?'),self) self.keepmeta.setToolTip(_('''If set, a copy of the original metadata for each merged book will be included, allowing for UnMerge. This includes your calibre custom columns. Leave off if you plan to distribute the epub to others.''')) self.keepmeta.setChecked(prefs['keepmeta']) self.l.addWidget(self.keepmeta) # self.showunmerge = QCheckBox(_('Show UnMerge Option?'),self) # self.showunmerge.setToolTip(_('''If set, the UnMerge Epub option will be shown on the EpubMerge menu. # Only Epubs merged with 'Keep UnMerge Metadata' can be UnMerged.''')) # self.showunmerge.setChecked(prefs['showunmerge']) # self.l.addWidget(self.showunmerge) horz = QHBoxLayout() horz.addWidget(QLabel(_("Add tags to merged books:"))) self.mergetags = QLineEdit(self) self.mergetags.setText(prefs['mergetags']) self.mergetags.setToolTip(_('Tags you enter here will be added to all new merged books')) horz.addWidget(self.mergetags) self.l.addLayout(horz) horz = QHBoxLayout() horz.addWidget(QLabel(_("Merged Book Word:"))) self.mergeword = QLineEdit(self) self.mergeword.setText(prefs['mergeword']) self.mergeword.setToolTip(_('''Word use to describe merged books in default title and summary. For people who don't like the word Anthology.''')) ## Defaults back to Anthology if cleared. horz.addWidget(self.mergeword) self.l.addLayout(horz) self.l.addSpacing(15) label = QLabel(_("These controls aren't plugin settings as such, but convenience buttons for setting Keyboard shortcuts and getting all the EpubMerge confirmation dialogs back again.")) label.setWordWrap(True) self.l.addWidget(label) self.l.addSpacing(5) keyboard_shortcuts_button = QPushButton(_('Keyboard shortcuts...'), self) keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin')) keyboard_shortcuts_button.clicked.connect(parent_dialog.edit_shortcuts) self.l.addWidget(keyboard_shortcuts_button) reset_confirmation_button = QPushButton(_('Reset disabled &confirmation dialogs'), self) reset_confirmation_button.setToolTip(_('Reset all show me again dialogs for the EpubMerge plugin')) reset_confirmation_button.clicked.connect(self.reset_dialogs) self.l.addWidget(reset_confirmation_button) view_prefs_button = QPushButton(_('View library preferences...'), self) view_prefs_button.setToolTip(_('View data stored in the library database for this plugin')) view_prefs_button.clicked.connect(self.view_prefs) self.l.addWidget(view_prefs_button) self.l.insertStretch(-1) def view_prefs(self): d = PrefsViewerDialog(self.plugin_action.gui, PREFS_NAMESPACE) d.exec_() def reset_dialogs(self): for key in dynamic.keys(): if key.startswith('epubmerge_') and key.endswith('_again') \ and dynamic[key] is False: dynamic[key] = True info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True, show_copy_button=False)
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) # --- Directory Options --- directory_group_box = QGroupBox(_('Default Unpack Directory:'), self) layout.addWidget(directory_group_box) directory_group_box_layout = QVBoxLayout() directory_group_box.setLayout(directory_group_box_layout) # Directory path Textbox # Load the textbox with the current preference setting self.directory_txtBox = QLineEdit(plugin_prefs['Unpack_Folder'], self) self.directory_txtBox.setToolTip(_('<p>Default directory to extract files to')) directory_group_box_layout.addWidget(self.directory_txtBox) self.directory_txtBox.setReadOnly(True) # Folder select button directory_button = QPushButton(_('Select/Change Unpack Directory'), self) directory_button.setToolTip(_('<p>Select/Change directory to extract files to.')) # Connect button to the getDirectory function directory_button.clicked.connect(self.getDirectory) directory_group_box_layout.addWidget(directory_button) self.default_folder_check = QCheckBox(_('Always use the Default Unpack Directory'), self) self.default_folder_check.setToolTip(_('<p>When unchecked... you will be prompted to select a destination '+ 'directory for the extracted content each time you use Mobiunpack.')) directory_group_box_layout.addWidget(self.default_folder_check) # Load the checkbox with the current preference setting self.default_folder_check.setChecked(plugin_prefs['Always_Use_Unpack_Folder']) misc_group_box = QGroupBox(_('Default settings:'), self) layout.addWidget(misc_group_box) misc_group_box_layout = QVBoxLayout() misc_group_box.setLayout(misc_group_box_layout) self.use_hd_images = QCheckBox(_('Always use HD images if present'), self) self.use_hd_images.setToolTip(_('<p>When checked... any HD images present in the kindlebook '+ 'will be used for creating the ePub.')) misc_group_box_layout.addWidget(self.use_hd_images) # Load the checkbox with the current preference setting self.use_hd_images.setChecked(plugin_prefs['Use_HD_Images']) combo_label = QLabel('Select epub version output:', self) misc_group_box_layout.addWidget(combo_label) self.epub_version_combobox = QComboBox() self.epub_version_combobox.setToolTip(_('<p>Select the type of OPF file to create.')) misc_group_box_layout.addWidget(self.epub_version_combobox) self.epub_version_combobox.addItems(['Auto-detect', 'ePub2', 'ePub3']) if plugin_prefs['Epub_Version'] == 'A': self.epub_version_combobox.setCurrentIndex(0) else: self.epub_version_combobox.setCurrentIndex(int(plugin_prefs['Epub_Version'])-1) def save_settings(self): # Save current dialog sttings back to JSON config file plugin_prefs['Unpack_Folder'] = unicode(self.directory_txtBox.displayText()) plugin_prefs['Always_Use_Unpack_Folder'] = self.default_folder_check.isChecked() plugin_prefs['Use_HD_Images'] = self.use_hd_images.isChecked() if unicode(self.epub_version_combobox.currentText()) == 'Auto-detect': plugin_prefs['Epub_Version'] = 'A' else: plugin_prefs['Epub_Version'] = unicode(self.epub_version_combobox.currentText())[4:] def getDirectory(self): c = choose_dir(self, _(PLUGIN_NAME + 'dir_chooser'), _('Select Default Directory To Unpack Kindle Book/Mobi To')) if c: self.directory_txtBox.setReadOnly(False) self.directory_txtBox.setText(c) self.directory_txtBox.setReadOnly(True) def validate(self): # This is just to catch the situation where somone might # manually enter a non-existent path in the Default path textbox. # Shouldn't be possible at this point. if not os.path.exists(self.directory_txtBox.text()): errmsg = '<p>The path specified for the Default Unpack folder does not exist.</p>' \ '<p>Your latest preference changes will <b>NOT</b> be saved!</p>' + \ '<p>You should configure again and make sure your settings are correct.' error_dialog(None, _(PLUGIN_NAME + ' v' + PLUGIN_VERSION), _(errmsg), show=True) return False return True
class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action layout = QVBoxLayout(self) self.setLayout(layout) # --- Directory Options --- directory_group_box = QGroupBox(_('Default Unpack Directory:'), self) layout.addWidget(directory_group_box) directory_group_box_layout = QVBoxLayout() directory_group_box.setLayout(directory_group_box_layout) # Directory path Textbox # Load the textbox with the current preference setting self.directory_txtBox = QLineEdit(plugin_prefs['Unpack_Folder'], self) self.directory_txtBox.setToolTip( _('<p>Default directory to extract files to')) directory_group_box_layout.addWidget(self.directory_txtBox) self.directory_txtBox.setReadOnly(True) # Folder select button directory_button = QPushButton(_('Select/Change Unpack Directory'), self) directory_button.setToolTip( _('<p>Select/Change directory to extract files to.')) # Connect button to the getDirectory function directory_button.clicked.connect(self.getDirectory) directory_group_box_layout.addWidget(directory_button) self.default_folder_check = QCheckBox( _('Always use the Default Unpack Directory'), self) self.default_folder_check.setToolTip( _('<p>When unchecked... you will be prompted to select a destination ' + 'directory for the extracted content each time you use Mobiunpack.' )) directory_group_box_layout.addWidget(self.default_folder_check) # Load the checkbox with the current preference setting self.default_folder_check.setChecked( plugin_prefs['Always_Use_Unpack_Folder']) misc_group_box = QGroupBox(_('Default settings:'), self) layout.addWidget(misc_group_box) misc_group_box_layout = QVBoxLayout() misc_group_box.setLayout(misc_group_box_layout) self.use_hd_images = QCheckBox(_('Always use HD images if present'), self) self.use_hd_images.setToolTip( _('<p>When checked... any HD images present in the kindlebook ' + 'will be used for creating the ePub.')) misc_group_box_layout.addWidget(self.use_hd_images) # Load the checkbox with the current preference setting self.use_hd_images.setChecked(plugin_prefs['Use_HD_Images']) combo_label = QLabel(_('Select epub version output:'), self) misc_group_box_layout.addWidget(combo_label) self.epub_version_combobox = QComboBox() self.epub_version_combobox.setToolTip( _('<p>Select the type of OPF file to create.')) misc_group_box_layout.addWidget(self.epub_version_combobox) self.epub_version_combobox.addItems( [_('Auto-detect'), _('ePub2'), _('ePub3')]) if plugin_prefs['Epub_Version'] == 'A': self.epub_version_combobox.setCurrentIndex(0) else: self.epub_version_combobox.setCurrentIndex( int(plugin_prefs['Epub_Version']) - 1) # --- ZIP mod Options --- zip_mod_group_box = QGroupBox(_('ZIP mod settings:'), self) layout.addWidget(zip_mod_group_box) zip_mod_group_box_layout = QVBoxLayout() zip_mod_group_box.setLayout(zip_mod_group_box_layout) zip_mod_combo_label = QLabel(_('Select zip compress type:'), self) zip_mod_group_box_layout.addWidget(zip_mod_combo_label) self.zip_compress_type_combobox = QComboBox() self.zip_compress_type_combobox.setToolTip( _('<p>Select the type of zip compress.')) zip_mod_group_box_layout.addWidget(self.zip_compress_type_combobox) self.zip_compress_type_combobox.addItems([_('STORE'), _('DEFLATE')]) if plugin_prefs['Zip_Compress_Type'] == 'S': self.zip_compress_type_combobox.setCurrentIndex(0) else: self.zip_compress_type_combobox.setCurrentIndex(1) # Directory path Textbox # Load the textbox with the current preference setting kindle_directory_label = QLabel(_('Kindle Content Directory:'), self) zip_mod_group_box_layout.addWidget(kindle_directory_label) self.kindle_directory_txtBox = QLineEdit( plugin_prefs['Kindle_Content_Folder'], self) self.kindle_directory_txtBox.setToolTip( _('<p>Kindle Content directory.')) zip_mod_group_box_layout.addWidget(self.kindle_directory_txtBox) self.kindle_directory_txtBox.setReadOnly(True) # Folder select button kindle_directory_button = QPushButton( _('Select/Change Kindle Content Directory'), self) kindle_directory_button.setToolTip( _('<p>Select/Change Kindle Content directory.')) # Connect button to the getDirectory function kindle_directory_button.clicked.connect(self.getDirectoryKindleContent) zip_mod_group_box_layout.addWidget(kindle_directory_button) self.delete_temp_files = QCheckBox(_('Always delete temporary files'), self) self.delete_temp_files.setToolTip( _('<p>When checked... Delete temporary files at end of unpack.')) zip_mod_group_box_layout.addWidget(self.delete_temp_files) # Load the checkbox with the current preference setting self.delete_temp_files.setChecked( plugin_prefs['Always_Delete_Temp_Files']) def save_settings(self): # Save current dialog sttings back to JSON config file plugin_prefs['Unpack_Folder'] = text_type( self.directory_txtBox.displayText()) plugin_prefs[ 'Always_Use_Unpack_Folder'] = self.default_folder_check.isChecked( ) plugin_prefs['Use_HD_Images'] = self.use_hd_images.isChecked() if text_type( self.epub_version_combobox.currentText()) == _('Auto-detect'): plugin_prefs['Epub_Version'] = 'A' else: plugin_prefs['Epub_Version'] = text_type( self.epub_version_combobox.currentText())[4:] # ZIP mod if text_type( self.zip_compress_type_combobox.currentText()) == _('STORE'): plugin_prefs['Zip_Compress_Type'] = 'S' else: plugin_prefs['Zip_Compress_Type'] = 'D' plugin_prefs['Kindle_Content_Folder'] = text_type( self.kindle_directory_txtBox.displayText()) plugin_prefs[ 'Always_Delete_Temp_Files'] = self.delete_temp_files.isChecked() def getDirectory(self): c = choose_dir( self, _(PLUGIN_NAME + 'dir_chooser'), _('Select Default Directory To Unpack Kindle Book/Mobi To')) if c: self.directory_txtBox.setReadOnly(False) self.directory_txtBox.setText(c) self.directory_txtBox.setReadOnly(True) def getDirectoryKindleContent(self): c = choose_dir(self, _(PLUGIN_NAME + 'dir_chooser'), _('Select Kindle Content Directory')) if c: self.kindle_directory_txtBox.setReadOnly(False) self.kindle_directory_txtBox.setText(c) self.kindle_directory_txtBox.setReadOnly(True) def validate(self): # This is just to catch the situation where somone might # manually enter a non-existent path in the Default path textbox. # Shouldn't be possible at this point. if not os.path.exists(self.directory_txtBox.text()): errmsg = _( '<p>The path specified for the Default Unpack folder does not exist.</p>' ) errmsg += _( '<p>Your latest preference changes will <b>NOT</b> be saved!</p>' ) errmsg += _( '<p>You should configure again and make sure your settings are correct.' ) error_dialog(None, _(PLUGIN_NAME + ' v' + PLUGIN_VERSION), _(errmsg), show=True) return False return True
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.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) 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() @property def ignore_collections_names(self): return self.ignore_collections_names_edit.text().strip()
class ConfigWidget(QWidget): # GUI definition def __init__(self): QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) self.ll = QHBoxLayout() self.l.addLayout(self.ll) self.label_exe = QLabel(_('&Prince executable:')) self.ll.addWidget(self.label_exe) self.exe = QLineEdit(self) self.exe.setText(prefs['prince_exe']) self.exe.setToolTip(_('<qt>Executable for the Prince program (command-line interface)</qt>')) self.ll.addWidget(self.exe) self.label_exe.setBuddy(self.exe) self.browse = QPushButton(_('&Browse') + '...', self) self.browse.setToolTip(_('<qt>Search the Prince executable in your computer</qt>')) self.browse.clicked.connect(self.select_exe) self.ll.addWidget(self.browse) self.lll = QHBoxLayout() self.l.addLayout(self.lll) self.label_fmts = QLabel(_('Preferred &formats:')) self.lll.addWidget(self.label_fmts) self.fmts = QLineEdit(self) self.fmts.setText(','.join(prefs['formats'])) self.fmts.setToolTip(_('<qt>Comma-separated list of preferred formats to use as source, the first that matches will be used</qt>')) self.lll.addWidget(self.fmts) self.label_fmts.setBuddy(self.fmts) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.l.addWidget(self.add_book) self.show_css = QCheckBox(_('&Show CSS in the Convert dialog')) self.show_css.setToolTip(_('<qt>Show by default the stylesheets in the Convert dialog</qt>')) self.show_css.setChecked(prefs['show_CSS']) self.l.addWidget(self.show_css) self.css_layout = QVBoxLayout() self.llll = QHBoxLayout() self.css_layout.addLayout(self.llll) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>List of custom stylesheets defined. Select one to edit</qt>')) self.css_list.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.CSS_list = prefs['custom_CSS_list'].copy() self.default_CSS = prefs['default_CSS'] if 'custom_CSS' in prefs: self.CSS_list[_('old')] = prefs['custom_CSS'] self.default_CSS = _('old') if self.default_CSS not in self.CSS_list: self.default_CSS = sorted(self.CSS_list, key=lambda x: x.lower())[0] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_list.currentIndexChanged.connect(self.set_css) self.llll.addWidget(self.css_list) self.css_rename = QPushButton(_('Re&name')) self.css_rename.setToolTip(_('<qt>Rename the current stylesheet to the name on the right</qt>')) self.css_rename.clicked.connect(self.rename_css) self.css_rename.setEnabled(False) self.llll.addWidget(self.css_rename) self.css_name = QLineEdit(self) self.css_name.setToolTip(_('<qt>Name for the new or renamed stylesheet</qt>')) self.css_name.setText(self.css_list.currentText()) self.css_name.textChanged.connect(self.check_names) self.llll.addWidget(self.css_name) self.css_add = QPushButton(_('A&dd')) self.css_add.setToolTip(_('<qt>Add a new empty stylesheet with the name on the left</qt>')) self.css_add.clicked.connect(self.add_css) self.css_add.setEnabled(False) self.llll.addWidget(self.css_add) self.css_remove = QPushButton(_('Re&move')) self.css_remove.setToolTip(_('<qt>Remove the current stylesheet</qt>')) self.css_remove.clicked.connect(self.remove_css) self.llll.addWidget(self.css_remove) self.css = TextEditWithTooltip() self.css.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css') self.css.setToolTip(_('<qt>Custom stylesheet that will be applied, if selected, to all Prince PDF conversions</qt>')) self.css_layout.addWidget(self.css) self.css_templates = QLabel(_('Book metadata can be used in the stylesheet. Anything between %(s1)s and %(s2)s will be processed as a calibre template. For instance, %(s3)s in the stylesheet will be replaced with the book title in the conversion.') % \ {'s1':'<span style="font-family:monospace ; font-weight:bold">@{@</span>', \ 's2':'<span style="font-family:monospace ; font-weight:bold">@}@</span>', \ 's3':'<span style="font-family:monospace ; font-weight:bold">@{@{title}@}@</span>'}) self.css_templates.setWordWrap(True) self.css_layout.addWidget(self.css_templates) self.css_box = QGroupBox(_('&Custom CSS:')) self.css_box.setLayout(self.css_layout) self.l.addWidget(self.css_box) self.lllll = QHBoxLayout() self.lllll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.lllll) self.defaults = QPushButton(_('&Restore defaults')) self.defaults.setToolTip(_('<qt>Restore the default settings</qt>')) self.defaults.clicked.connect(self.restore_defaults) self.lllll.addWidget(self.defaults, alignment=Qt.AlignLeft) self.warning = QLabel(_('<b>Warning</b>: Deletes modified stylesheets')) self.lllll.addWidget(self.warning) self.adjustSize() def select_exe(self): ''' Create a dialog to select the Prince executable ''' dialog = QFileDialog() dialog.setFileMode(QFileDialog.ExistingFile) filename = dialog.getOpenFileName(self, _('Select Prince executable'), '', '') if filename: try: self.exe.setText(filename) except(TypeError): self.exe.setText(filename[0]) def restore_defaults(self): ''' Restore the default settings ''' self.exe.setText(prefs.defaults['prince_exe']) self.fmts.setText(','.join(prefs.defaults['formats']).lower()) self.show_css.setChecked(prefs.defaults['show_CSS']) self.add_book.setChecked(prefs.defaults['add_book']) self.css_list.currentIndexChanged.disconnect() self.css_list.clear() self.CSS_list = prefs.defaults['custom_CSS_list'].copy() self.default_CSS = prefs.defaults['default_CSS'] for key in sorted(self.CSS_list, key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS)) self.css_name.setText(self.default_CSS) self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css') self.css_list.currentIndexChanged.connect(self.set_css) def save_settings(self): ''' Save the current settings ''' prefs['prince_exe'] = unicode(self.exe.text()) prefs['formats'] = unicode(self.fmts.text().lower()).split(',') prefs['show_CSS'] = self.show_css.isChecked() prefs['add_book'] = self.add_book.isChecked() self.set_css() prefs['default_CSS'] = self.default_CSS prefs['custom_CSS_list'] = self.CSS_list.copy() if 'custom_CSS' in prefs: del prefs['custom_CSS'] def set_css(self): ''' Fill the CSS text box with the selected stylesheet ''' self.CSS_list[self.default_CSS] = unicode(self.css.toPlainText()) self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS],'css') self.css_name.setText(self.css_list.currentText()) def add_css(self): ''' Add a new stylesheet ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog(self, _('Cannot add stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True) else: self.CSS_list[name] = '' self.css_list.addItem(name, name) self.css_list.setCurrentIndex(self.css_list.findText(name)) self.css_add.setEnabled(False) self.css_rename.setEnabled(False) def remove_css(self): ''' Remove an existing stylesheet ''' from calibre.gui2 import error_dialog if (self.css_list.count() > 1): self.css_list.currentIndexChanged.disconnect() self.css_list.removeItem(self.css_list.currentIndex()) del self.CSS_list[self.default_CSS] self.default_CSS = unicode(self.css_list.currentText()) self.css.load_text(self.CSS_list[self.default_CSS],'css') self.css_list.currentIndexChanged.connect(self.set_css) self.css_name.setText(self.css_list.currentText()) else: error_dialog(self, _('Cannot delete the last stylesheet'), _('The last stylesheet cannot be removed. You can rename it and/or remove its contents.'), show=True) def rename_css(self): ''' Rename a stylesheet ''' from calibre.gui2 import error_dialog name = unicode(self.css_name.text()) if name in self.CSS_list: error_dialog(self, _('Cannot rename stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True) else: self.CSS_list[name] = self.CSS_list.pop(self.default_CSS) self.css_list.setItemText(self.css_list.currentIndex(),name) self.default_CSS = name def check_names(self, text): name = unicode(text) if name in self.CSS_list: self.css_add.setEnabled(False) self.css_rename.setEnabled(False) else: self.css_add.setEnabled(True) self.css_rename.setEnabled(True)
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip( _('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip( _('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>' )) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex( self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip( _('<qt>Specify additional command-line arguments for the conversion</qt>' )) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80, 20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text( self.replace_templates( prefs['custom_CSS_list'][prefs['default_CSS']]), 'css') self.css1.setToolTip( _('<qt>This stylesheet can be modified<br/>The default can be configured</qt>' )) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip( i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip( _('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip( i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>' )) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip( _('<qt>Show/hide the additional styles used for the conversion</qt>' )) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text( self.replace_templates(prefs['custom_CSS_list'][style]), 'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@', text, re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print( _('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile(mode='w+') custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile(mode='w+') for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels) process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog( self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()
class ConditionEditor(QWidget): # {{{ ACTION_MAP = { 'bool' : ( (_('is true'), 'is true',), (_('is false'), 'is false'), (_('is undefined'), 'is undefined') ), 'ondevice' : ( (_('is true'), 'is set',), (_('is false'), 'is not set'), ), 'identifiers' : ( (_('has id'), 'has id'), (_('does not have id'), 'does not have id'), ), 'int' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt') ), 'datetime' : ( (_('is equal to'), 'eq'), (_('is less than'), 'lt'), (_('is greater than'), 'gt'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), (_('is more days ago than'), 'older count days'), (_('is fewer days ago than'), 'count_days'), (_('is more days from now than'), 'newer future days'), (_('is fewer days from now than'), 'older future days') ), 'multiple' : ( (_('has'), 'has'), (_('does not have'), 'does not have'), (_('has pattern'), 'has pattern'), (_('does not have pattern'), 'does not have pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), 'single' : ( (_('is'), 'is'), (_('is not'), 'is not'), (_('matches pattern'), 'matches pattern'), (_('does not match pattern'), 'does not match pattern'), (_('is set'), 'is set'), (_('is not set'), 'is not set'), ), } for x in ('float', 'rating'): ACTION_MAP[x] = ACTION_MAP['int'] def __init__(self, fm, parent=None): QWidget.__init__(self, parent) self.fm = fm self.action_map = self.ACTION_MAP self.l = l = QGridLayout(self) self.setLayout(l) texts = _('If the ___ column ___ values') try: one, two, three = texts.split('___') except: one, two, three = 'If the ', ' column ', ' value ' self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) l.addWidget(self.value_box, 0, 5) self.column_box.addItem('', '') for key in sorted( conditionable_columns(fm), key=lambda(key): sort_key(fm[key]['name'])): self.column_box.addItem(fm[key]['name'], key) self.column_box.setCurrentIndex(0) self.column_box.currentIndexChanged.connect(self.init_action_box) self.action_box.currentIndexChanged.connect(self.init_value_box) for b in (self.column_box, self.action_box): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(20) @dynamic_property def current_col(self): def fget(self): idx = self.column_box.currentIndex() return unicode(self.column_box.itemData(idx) or '') def fset(self, val): for idx in range(self.column_box.count()): c = unicode(self.column_box.itemData(idx) or '') if c == val: self.column_box.setCurrentIndex(idx) return raise ValueError('Column %r not found'%val) return property(fget=fget, fset=fset) @dynamic_property def current_action(self): def fget(self): idx = self.action_box.currentIndex() return unicode(self.action_box.itemData(idx) or '') def fset(self, val): for idx in range(self.action_box.count()): c = unicode(self.action_box.itemData(idx) or '') if c == val: self.action_box.setCurrentIndex(idx) return raise ValueError('Action %r not valid for current column'%val) return property(fget=fget, fset=fset) @property def current_val(self): ans = unicode(self.value_box.text()).strip() if self.current_col == 'languages': rmap = {lower(v):k for k, v in lang_map().iteritems()} ans = rmap.get(lower(ans), ans) return ans @dynamic_property def condition(self): def fget(self): c, a, v = (self.current_col, self.current_action, self.current_val) if not c or not a: return None return (c, a, v) def fset(self, condition): c, a, v = condition if not v: v = '' v = v.strip() self.current_col = c self.current_action = a self.value_box.setText(v) return property(fget=fget, fset=fset) def init_action_box(self): self.action_box.blockSignals(True) self.action_box.clear() self.action_box.addItem('', '') col = self.current_col if col: m = self.fm[col] dt = m['datatype'] if dt in self.action_map: actions = self.action_map[dt] else: if col == 'ondevice': k = 'ondevice' elif col == 'identifiers': k = 'identifiers' else: k = 'multiple' if m['is_multiple'] else 'single' actions = self.action_map[k] for text, key in actions: self.action_box.addItem(text, key) self.action_box.setCurrentIndex(0) self.action_box.blockSignals(False) self.init_value_box() def init_value_box(self): self.value_box.setEnabled(True) self.value_box.setText('') self.value_box.setInputMask('') self.value_box.setValidator(None) col = self.current_col if not col: return action = self.current_action if not action: return m = self.fm[col] dt = m['datatype'] tt = '' if col == 'identifiers': tt = _('Enter either an identifier type or an ' 'identifier type and value of the form identifier:value') elif col == 'languages': tt = _('Enter a 3 letter ISO language code, like fra for French' ' or deu for German or eng for English. You can also use' ' the full language name, in which case calibre will try to' ' automatically convert it to the language code.') elif dt in ('int', 'float', 'rating'): tt = _('Enter a number') v = QIntValidator if dt == 'int' else QDoubleValidator self.value_box.setValidator(v(self.value_box)) elif dt == 'datetime': if action == 'count_days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days old the item can be. Zero is today. ' 'Dates in the future always match') elif action == 'older count days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days old the item can be. Zero is today. ' 'Dates in the future never match') elif action == 'older future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the maximum days in the future the item can be. ' 'Zero is today. Dates in the past always match') elif action == 'newer future days': self.value_box.setValidator(QIntValidator(self.value_box)) tt = _('Enter the minimum days in the future the item can be. ' 'Zero is today. Dates in the past never match') else: self.value_box.setInputMask('9999-99-99') tt = _('Enter a date in the format YYYY-MM-DD') else: tt = _('Enter a string.') if 'pattern' in action: tt = _('Enter a regular expression') elif m.get('is_multiple', False): tt += '\n' + _('You can match multiple values by separating' ' them with %s')%m['is_multiple']['ui_to_list'] self.value_box.setToolTip(tt) if action in ('is set', 'is not set', 'is true', 'is false', 'is undefined'): self.value_box.setEnabled(False)
class CheckLibraryDialog(QDialog): def __init__(self, parent, db): QDialog.__init__(self, parent) self.db = db self.setWindowTitle(_('Check library -- Problems found')) self.setWindowIcon(QIcon(I('debug.png'))) self._tl = QHBoxLayout() self.setLayout(self._tl) self.splitter = QSplitter(self) self.left = QWidget(self) self.splitter.addWidget(self.left) self.helpw = QTextEdit(self) self.splitter.addWidget(self.helpw) self._tl.addWidget(self.splitter) self._layout = QVBoxLayout() self.left.setLayout(self._layout) self.helpw.setReadOnly(True) self.helpw.setText( _('''\ <h1>Help</h1> <p>calibre stores the list of your books and their metadata in a database. The actual book files and covers are stored as normal files in the calibre library folder. The database contains a list of the files and covers belonging to each book entry. This tool checks that the actual files in the library folder on your computer match the information in the database.</p> <p>The result of each type of check is shown to the left. The various checks are: </p> <ul> <li><b>Invalid titles</b>: These are files and folders appearing in the library where books titles should, but that do not have the correct form to be a book title.</li> <li><b>Extra titles</b>: These are extra files in your calibre library that appear to be correctly-formed titles, but have no corresponding entries in the database</li> <li><b>Invalid authors</b>: These are files appearing in the library where only author folders should be.</li> <li><b>Extra authors</b>: These are folders in the calibre library that appear to be authors but that do not have entries in the database</li> <li><b>Missing book formats</b>: These are book formats that are in the database but have no corresponding format file in the book's folder. <li><b>Extra book formats</b>: These are book format files found in the book's folder but not in the database. <li><b>Unknown files in books</b>: These are extra files in the folder of each book that do not correspond to a known format or cover file.</li> <li><b>Missing cover files</b>: These represent books that are marked in the database as having covers but the actual cover files are missing.</li> <li><b>Cover files not in database</b>: These are books that have cover files but are marked as not having covers in the database.</li> <li><b>Folder raising exception</b>: These represent folders in the calibre library that could not be processed/understood by this tool.</li> </ul> <p>There are two kinds of automatic fixes possible: <i>Delete marked</i> and <i>Fix marked</i>.</p> <p><i>Delete marked</i> is used to remove extra files/folders/covers that have no entries in the database. Check the box next to the item you want to delete. Use with caution.</p> <p><i>Fix marked</i> is applicable only to covers and missing formats (the three lines marked 'fixable'). In the case of missing cover files, checking the fixable box and pushing this button will tell calibre that there is no cover for all of the books listed. Use this option if you are not going to restore the covers from a backup. In the case of extra cover files, checking the fixable box and pushing this button will tell calibre that the cover files it found are correct for all the books listed. Use this when you are not going to delete the file(s). In the case of missing formats, checking the fixable box and pushing this button will tell calibre that the formats are really gone. Use this if you are not going to restore the formats from a backup.</p> ''')) self.log = QTreeWidget(self) self.log.itemChanged.connect(self.item_changed) self.log.itemExpanded.connect(self.item_expanded_or_collapsed) self.log.itemCollapsed.connect(self.item_expanded_or_collapsed) self._layout.addWidget(self.log) self.check_button = QPushButton(_('&Run the check again')) self.check_button.setDefault(False) self.check_button.clicked.connect(self.run_the_check) self.copy_button = QPushButton(_('Copy &to clipboard')) self.copy_button.setDefault(False) self.copy_button.clicked.connect(self.copy_to_clipboard) self.ok_button = QPushButton(_('&Done')) self.ok_button.setDefault(True) self.ok_button.clicked.connect(self.accept) self.mark_delete_button = QPushButton(_('Mark &all for delete')) self.mark_delete_button.setToolTip(_('Mark all deletable subitems')) self.mark_delete_button.setDefault(False) self.mark_delete_button.clicked.connect(self.mark_for_delete) self.delete_button = QPushButton(_('Delete &marked')) self.delete_button.setToolTip( _('Delete marked files (checked subitems)')) self.delete_button.setDefault(False) self.delete_button.clicked.connect(self.delete_marked) self.mark_fix_button = QPushButton(_('Mar&k all for fix')) self.mark_fix_button.setToolTip(_('Mark all fixable items')) self.mark_fix_button.setDefault(False) self.mark_fix_button.clicked.connect(self.mark_for_fix) self.fix_button = QPushButton(_('&Fix marked')) self.fix_button.setDefault(False) self.fix_button.setEnabled(False) self.fix_button.setToolTip( _('Fix marked sections (checked fixable items)')) self.fix_button.clicked.connect(self.fix_items) self.bbox = QGridLayout() self.bbox.addWidget(self.check_button, 0, 0) self.bbox.addWidget(self.copy_button, 0, 1) self.bbox.addWidget(self.ok_button, 0, 2) self.bbox.addWidget(self.mark_delete_button, 1, 0) self.bbox.addWidget(self.delete_button, 1, 1) self.bbox.addWidget(self.mark_fix_button, 2, 0) self.bbox.addWidget(self.fix_button, 2, 1) h = QHBoxLayout() ln = QLabel(_('Names to ignore:')) h.addWidget(ln) self.name_ignores = QLineEdit() self.name_ignores.setText( db.prefs.get('check_library_ignore_names', '')) self.name_ignores.setToolTip( _('Enter comma-separated standard file name wildcards, such as synctoy*.dat' )) ln.setBuddy(self.name_ignores) h.addWidget(self.name_ignores) le = QLabel(_('Extensions to ignore:')) h.addWidget(le) self.ext_ignores = QLineEdit() self.ext_ignores.setText( db.prefs.get('check_library_ignore_extensions', '')) self.ext_ignores.setToolTip( _('Enter comma-separated extensions without a leading dot. Used only in book folders' )) le.setBuddy(self.ext_ignores) h.addWidget(self.ext_ignores) self._layout.addLayout(h) self._layout.addLayout(self.bbox) self.resize(950, 500) def do_exec(self): self.run_the_check() probs = 0 for c in self.problem_count: probs += self.problem_count[c] if probs == 0: return False self.exec_() return True def accept(self): self.db.new_api.set_pref('check_library_ignore_extensions', unicode_type(self.ext_ignores.text())) self.db.new_api.set_pref('check_library_ignore_names', unicode_type(self.name_ignores.text())) QDialog.accept(self) def box_to_list(self, txt): return [f.strip() for f in txt.split(',') if f.strip()] def run_the_check(self): checker = CheckLibrary(self.db.library_path, self.db) checker.scan_library( self.box_to_list(unicode_type(self.name_ignores.text())), self.box_to_list(unicode_type(self.ext_ignores.text()))) plaintext = [] def builder(tree, checker, check): attr, h, checkable, fixable = check list = getattr(checker, attr, None) if list is None: self.problem_count[attr] = 0 return else: self.problem_count[attr] = len(list) tl = Item() tl.setText(0, h) if fixable and list: tl.setText(1, _('(fixable)')) tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) tl.setCheckState(1, False) else: tl.setFlags(Qt.ItemIsEnabled) self.top_level_items[attr] = tl for problem in list: it = Item() if checkable: it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) it.setCheckState(1, False) else: it.setFlags(Qt.ItemIsEnabled) it.setText(0, problem[0]) it.setData(0, Qt.UserRole, problem[2]) it.setText(1, problem[1]) tl.addChild(it) self.all_items.append(it) plaintext.append(','.join([h, problem[0], problem[1]])) tree.addTopLevelItem(tl) t = self.log t.clear() t.setColumnCount(2) t.setHeaderLabels([_('Name'), _('Path from library')]) self.all_items = [] self.top_level_items = {} self.problem_count = {} for check in CHECKS: builder(t, checker, check) t.resizeColumnToContents(0) t.resizeColumnToContents(1) self.delete_button.setEnabled(False) self.fix_button.setEnabled(False) self.text_results = '\n'.join(plaintext) def item_expanded_or_collapsed(self, item): self.log.resizeColumnToContents(0) self.log.resizeColumnToContents(1) def item_changed(self, item, column): self.fix_button.setEnabled(False) for it in self.top_level_items.values(): if it.checkState(1): self.fix_button.setEnabled(True) self.delete_button.setEnabled(False) for it in self.all_items: if it.checkState(1): self.delete_button.setEnabled(True) return def mark_for_fix(self): for it in self.top_level_items.values(): if it.flags() & Qt.ItemIsUserCheckable: it.setCheckState(1, Qt.Checked) def mark_for_delete(self): for it in self.all_items: if it.flags() & Qt.ItemIsUserCheckable: it.setCheckState(1, Qt.Checked) def delete_marked(self): if not confirm( '<p>' + _('The marked files and folders will be ' '<b>permanently deleted</b>. Are you sure?') + '</p>', 'check_library_editor_delete', self): return # Sort the paths in reverse length order so that we can be sure that # if an item is in another item, the sub-item will be deleted first. items = sorted(self.all_items, key=lambda x: len(x.text(1)), reverse=True) for it in items: if it.checkState(1): try: p = os.path.join(self.db.library_path, unicode_type(it.text(1))) if os.path.isdir(p): delete_tree(p) else: delete_file(p) except: prints( 'failed to delete', os.path.join(self.db.library_path, unicode_type(it.text(1)))) self.run_the_check() def fix_missing_formats(self): tl = self.top_level_items['missing_formats'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) all = self.db.formats(id, index_is_id=True, verify_formats=False) all = {f.strip() for f in all.split(',')} if all else set() valid = self.db.formats(id, index_is_id=True, verify_formats=True) valid = {f.strip() for f in valid.split(',')} if valid else set() for fmt in all - valid: self.db.remove_format(id, fmt, index_is_id=True, db_only=True) def fix_missing_covers(self): tl = self.top_level_items['missing_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) self.db.set_has_cover(id, False) def fix_extra_covers(self): tl = self.top_level_items['extra_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) self.db.set_has_cover(id, True) def fix_items(self): for check in CHECKS: attr = check[0] fixable = check[3] tl = self.top_level_items[attr] if fixable and tl.checkState(1): func = getattr(self, 'fix_' + attr, None) if func is not None and callable(func): func() self.run_the_check() def copy_to_clipboard(self): QApplication.clipboard().setText(self.text_results)
class CheckLibraryDialog(QDialog): def __init__(self, parent, db): QDialog.__init__(self, parent) self.db = db self.setWindowTitle(_('Check Library -- Problems Found')) self.setWindowIcon(QIcon(I('debug.png'))) self._tl = QHBoxLayout() self.setLayout(self._tl) self.splitter = QSplitter(self) self.left = QWidget(self) self.splitter.addWidget(self.left) self.helpw = QTextEdit(self) self.splitter.addWidget(self.helpw) self._tl.addWidget(self.splitter) self._layout = QVBoxLayout() self.left.setLayout(self._layout) self.helpw.setReadOnly(True) self.helpw.setText(_('''\ <h1>Help</h1> <p>calibre stores the list of your books and their metadata in a database. The actual book files and covers are stored as normal files in the calibre library folder. The database contains a list of the files and covers belonging to each book entry. This tool checks that the actual files in the library folder on your computer match the information in the database.</p> <p>The result of each type of check is shown to the left. The various checks are: </p> <ul> <li><b>Invalid titles</b>: These are files and folders appearing in the library where books titles should, but that do not have the correct form to be a book title.</li> <li><b>Extra titles</b>: These are extra files in your calibre library that appear to be correctly-formed titles, but have no corresponding entries in the database</li> <li><b>Invalid authors</b>: These are files appearing in the library where only author folders should be.</li> <li><b>Extra authors</b>: These are folders in the calibre library that appear to be authors but that do not have entries in the database</li> <li><b>Missing book formats</b>: These are book formats that are in the database but have no corresponding format file in the book's folder. <li><b>Extra book formats</b>: These are book format files found in the book's folder but not in the database. <li><b>Unknown files in books</b>: These are extra files in the folder of each book that do not correspond to a known format or cover file.</li> <li><b>Missing cover files</b>: These represent books that are marked in the database as having covers but the actual cover files are missing.</li> <li><b>Cover files not in database</b>: These are books that have cover files but are marked as not having covers in the database.</li> <li><b>Folder raising exception</b>: These represent folders in the calibre library that could not be processed/understood by this tool.</li> </ul> <p>There are two kinds of automatic fixes possible: <i>Delete marked</i> and <i>Fix marked</i>.</p> <p><i>Delete marked</i> is used to remove extra files/folders/covers that have no entries in the database. Check the box next to the item you want to delete. Use with caution.</p> <p><i>Fix marked</i> is applicable only to covers and missing formats (the three lines marked 'fixable'). In the case of missing cover files, checking the fixable box and pushing this button will tell calibre that there is no cover for all of the books listed. Use this option if you are not going to restore the covers from a backup. In the case of extra cover files, checking the fixable box and pushing this button will tell calibre that the cover files it found are correct for all the books listed. Use this when you are not going to delete the file(s). In the case of missing formats, checking the fixable box and pushing this button will tell calibre that the formats are really gone. Use this if you are not going to restore the formats from a backup.</p> ''')) self.log = QTreeWidget(self) self.log.itemChanged.connect(self.item_changed) self.log.itemExpanded.connect(self.item_expanded_or_collapsed) self.log.itemCollapsed.connect(self.item_expanded_or_collapsed) self._layout.addWidget(self.log) self.check_button = QPushButton(_('&Run the check again')) self.check_button.setDefault(False) self.check_button.clicked.connect(self.run_the_check) self.copy_button = QPushButton(_('Copy &to clipboard')) self.copy_button.setDefault(False) self.copy_button.clicked.connect(self.copy_to_clipboard) self.ok_button = QPushButton(_('&Done')) self.ok_button.setDefault(True) self.ok_button.clicked.connect(self.accept) self.mark_delete_button = QPushButton(_('Mark &all for delete')) self.mark_delete_button.setToolTip(_('Mark all deletable subitems')) self.mark_delete_button.setDefault(False) self.mark_delete_button.clicked.connect(self.mark_for_delete) self.delete_button = QPushButton(_('Delete &marked')) self.delete_button.setToolTip(_('Delete marked files (checked subitems)')) self.delete_button.setDefault(False) self.delete_button.clicked.connect(self.delete_marked) self.mark_fix_button = QPushButton(_('Mar&k all for fix')) self.mark_fix_button.setToolTip(_('Mark all fixable items')) self.mark_fix_button.setDefault(False) self.mark_fix_button.clicked.connect(self.mark_for_fix) self.fix_button = QPushButton(_('&Fix marked')) self.fix_button.setDefault(False) self.fix_button.setEnabled(False) self.fix_button.setToolTip(_('Fix marked sections (checked fixable items)')) self.fix_button.clicked.connect(self.fix_items) self.bbox = QGridLayout() self.bbox.addWidget(self.check_button, 0, 0) self.bbox.addWidget(self.copy_button, 0, 1) self.bbox.addWidget(self.ok_button, 0, 2) self.bbox.addWidget(self.mark_delete_button, 1, 0) self.bbox.addWidget(self.delete_button, 1, 1) self.bbox.addWidget(self.mark_fix_button, 2, 0) self.bbox.addWidget(self.fix_button, 2, 1) h = QHBoxLayout() ln = QLabel(_('Names to ignore:')) h.addWidget(ln) self.name_ignores = QLineEdit() self.name_ignores.setText(db.prefs.get('check_library_ignore_names', '')) self.name_ignores.setToolTip( _('Enter comma-separated standard file name wildcards, such as synctoy*.dat')) ln.setBuddy(self.name_ignores) h.addWidget(self.name_ignores) le = QLabel(_('Extensions to ignore')) h.addWidget(le) self.ext_ignores = QLineEdit() self.ext_ignores.setText(db.prefs.get('check_library_ignore_extensions', '')) self.ext_ignores.setToolTip( _('Enter comma-separated extensions without a leading dot. Used only in book folders')) le.setBuddy(self.ext_ignores) h.addWidget(self.ext_ignores) self._layout.addLayout(h) self._layout.addLayout(self.bbox) self.resize(950, 500) def do_exec(self): self.run_the_check() probs = 0 for c in self.problem_count: probs += self.problem_count[c] if probs == 0: return False self.exec_() return True def accept(self): self.db.new_api.set_pref('check_library_ignore_extensions', unicode(self.ext_ignores.text())) self.db.new_api.set_pref('check_library_ignore_names', unicode(self.name_ignores.text())) QDialog.accept(self) def box_to_list(self, txt): return [f.strip() for f in txt.split(',') if f.strip()] def run_the_check(self): checker = CheckLibrary(self.db.library_path, self.db) checker.scan_library(self.box_to_list(unicode(self.name_ignores.text())), self.box_to_list(unicode(self.ext_ignores.text()))) plaintext = [] def builder(tree, checker, check): attr, h, checkable, fixable = check list = getattr(checker, attr, None) if list is None: self.problem_count[attr] = 0 return else: self.problem_count[attr] = len(list) tl = Item() tl.setText(0, h) if fixable and list: tl.setText(1, _('(fixable)')) tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) tl.setCheckState(1, False) else: tl.setFlags(Qt.ItemIsEnabled) self.top_level_items[attr] = tl for problem in list: it = Item() if checkable: it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) it.setCheckState(1, False) else: it.setFlags(Qt.ItemIsEnabled) it.setText(0, problem[0]) it.setData(0, Qt.UserRole, problem[2]) it.setText(1, problem[1]) tl.addChild(it) self.all_items.append(it) plaintext.append(','.join([h, problem[0], problem[1]])) tree.addTopLevelItem(tl) t = self.log t.clear() t.setColumnCount(2) t.setHeaderLabels([_('Name'), _('Path from library')]) self.all_items = [] self.top_level_items = {} self.problem_count = {} for check in CHECKS: builder(t, checker, check) t.resizeColumnToContents(0) t.resizeColumnToContents(1) self.delete_button.setEnabled(False) self.fix_button.setEnabled(False) self.text_results = '\n'.join(plaintext) def item_expanded_or_collapsed(self, item): self.log.resizeColumnToContents(0) self.log.resizeColumnToContents(1) def item_changed(self, item, column): self.fix_button.setEnabled(False) for it in self.top_level_items.values(): if it.checkState(1): self.fix_button.setEnabled(True) self.delete_button.setEnabled(False) for it in self.all_items: if it.checkState(1): self.delete_button.setEnabled(True) return def mark_for_fix(self): for it in self.top_level_items.values(): if it.flags() & Qt.ItemIsUserCheckable: it.setCheckState(1, Qt.Checked) def mark_for_delete(self): for it in self.all_items: if it.flags() & Qt.ItemIsUserCheckable: it.setCheckState(1, Qt.Checked) def delete_marked(self): if not confirm('<p>'+_('The marked files and folders will be ' '<b>permanently deleted</b>. Are you sure?') +'</p>', 'check_library_editor_delete', self): return # Sort the paths in reverse length order so that we can be sure that # if an item is in another item, the sub-item will be deleted first. items = sorted(self.all_items, key=lambda x: len(x.text(1)), reverse=True) for it in items: if it.checkState(1): try: p = os.path.join(self.db.library_path ,unicode(it.text(1))) if os.path.isdir(p): delete_tree(p) else: delete_file(p) except: prints('failed to delete', os.path.join(self.db.library_path, unicode(it.text(1)))) self.run_the_check() def fix_missing_formats(self): tl = self.top_level_items['missing_formats'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) all = self.db.formats(id, index_is_id=True, verify_formats=False) all = set([f.strip() for f in all.split(',')]) if all else set() valid = self.db.formats(id, index_is_id=True, verify_formats=True) valid = set([f.strip() for f in valid.split(',')]) if valid else set() for fmt in all-valid: self.db.remove_format(id, fmt, index_is_id=True, db_only=True) def fix_missing_covers(self): tl = self.top_level_items['missing_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) self.db.set_has_cover(id, False) def fix_extra_covers(self): tl = self.top_level_items['extra_covers'] child_count = tl.childCount() for i in range(0, child_count): item = tl.child(i) id = int(item.data(0, Qt.UserRole)) self.db.set_has_cover(id, True) def fix_items(self): for check in CHECKS: attr = check[0] fixable = check[3] tl = self.top_level_items[attr] if fixable and tl.checkState(1): func = getattr(self, 'fix_' + attr, None) if func is not None and callable(func): func() self.run_the_check() def copy_to_clipboard(self): QApplication.clipboard().setText(self.text_results)
class ConvertDialog(QDialog): hide_text = _('&Hide styles') show_text = _('&Show styles') prince_log = '' prince_file = '' prince_css = '' # GUI definition def __init__(self, mi, fmt, opf, oeb, icon): ''' :param mi: The book metadata :param fmt: The source format used for conversion :param opf: The path to the OPF file :param oeb: An OEB object for the unpacked book :param icon: The window icon ''' self.opf = opf self.oeb = oeb self.mi = mi # The unpacked book needs to be parsed before, to read the contents # of the prince-style file, if it exists self.parse() QDialog.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.setWindowTitle(_('Convert to PDF with Prince')) self.setWindowIcon(icon) self.l = QVBoxLayout() self.setLayout(self.l) self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title) self.l.addWidget(self.title_label) self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt) self.l.addWidget(self.format_label) self.add_book = QCheckBox(_('&Add PDF to the book record')) self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>')) self.add_book.setChecked(prefs['add_book']) self.add_book.stateChanged.connect(self.set_add_book) self.l.addWidget(self.add_book) self.ll = QHBoxLayout() self.ll.setAlignment(Qt.AlignLeft) self.l.addLayout(self.ll) self.label_css = QLabel(_('&Custom style:')) self.ll.addWidget(self.label_css) self.css_list = QComboBox() self.css_list.setToolTip(_('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>')) for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()): self.css_list.addItem(key, key) self.css_list.setCurrentIndex(self.css_list.findText(prefs['default_CSS'])) self.css_list.currentIndexChanged.connect(self.set_css) self.ll.addWidget(self.css_list) self.label_css.setBuddy(self.css_list) self.ll_ = QHBoxLayout() self.l.addLayout(self.ll_) self.label_args = QLabel(_('A&dditional command-line arguments:')) self.ll_.addWidget(self.label_args) self.args = QLineEdit(self) self.args.setText(prefs['custom_args_list'][prefs['default_CSS']]) self.args.setToolTip(_('<qt>Specify additional command-line arguments for the conversion</qt>')) self.ll_.addWidget(self.args) self.label_args.setBuddy(self.args) self.css = QTabWidget() self.l.addWidget(self.css) self.css1 = TextEditWithTooltip(self, expected_geometry=(80,20)) self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][prefs['default_CSS']]),'css') self.css1.setToolTip(_('<qt>This stylesheet can be modified<br/>The default can be configured</qt>')) i = self.css.addTab(self.css1, _('C&ustom CSS')) self.css.setTabToolTip(i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>')) monofont = QFont('') monofont.setStyleHint(QFont.TypeWriter) if (self.prince_css): self.css2 = QPlainTextEdit() self.css2.setStyleSheet('* { font-family: monospace }') self.css2.setLineWrapMode(QPlainTextEdit.NoWrap) self.css2.setPlainText(self.prince_css) self.css2.setReadOnly(True) self.css2.setToolTip(_('<qt>This stylesheet cannot be modified</qt>')) i = self.css.addTab(self.css2, _('&Book CSS')) self.css.setTabToolTip(i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>')) self.ll = QHBoxLayout() self.l.addLayout(self.ll) if (prefs['show_CSS']): self.toggle = QPushButton(self.hide_text, self) else: self.toggle = QPushButton(self.show_text, self) self.toggle.setToolTip(_('<qt>Show/hide the additional styles used for the conversion</qt>')) self.toggle.clicked.connect(self.toggle_tabs) self.convert = QPushButton(_('Con&vert'), self) self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>')) self.convert.setDefault(True) self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel) self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole) self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole) self.l.addWidget(self.buttons) self.buttons.accepted.connect(self.prince_convert) self.buttons.rejected.connect(self.reject) if (not prefs['show_CSS']): self.css.hide() self.adjustSize() def toggle_tabs(self): ''' Enable/disable the CSS tabs, and store the setting ''' if (self.css.isVisible()): self.css.hide() self.label_args.hide() self.args.hide() self.toggle.setText(self.show_text) self.adjustSize() else: self.css.show() self.label_args.show() self.args.show() self.toggle.setText(self.hide_text) self.adjustSize() prefs['show_CSS'] = self.css.isVisible() def set_add_book(self): ''' Save the status of the add_book checkbox ''' prefs['add_book'] = self.add_book.isChecked() def set_css(self): ''' Fill the custom CSS text box with the selected stylesheet (and command-line arguments) ''' style = unicode(self.css_list.currentText()) self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][style]),'css') self.args.setText(prefs['custom_args_list'][style]) prefs['default_CSS'] = style def parse(self): ''' Parse the unpacked OPF file to find and read the prince-style file ''' from calibre.constants import DEBUG from os.path import dirname, join from lxml import etree import codecs if DEBUG: print(_('Parsing book...')) opf_dir = dirname(self.opf) root = etree.parse(self.opf).getroot() metadata = root.find('{*}metadata') for meta in metadata.findall("{*}meta[@name='prince-style']"): prince_id = meta.get('content') for item in self.oeb.manifest: if (item.id == prince_id): self.prince_file = item.href break if (self.prince_file): fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8') self.prince_css = fl.read() fl.close() def replace_templates(self, text): ''' Replace templates (enclosed by '@{@', '@}@') in the input text ''' import re import json from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.constants import DEBUG matches = list(re.finditer('@{@(.+?)@}@',text,re.DOTALL)) results = {} for match in reversed(matches): result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi) # Escape quotes, backslashes and newlines result = re.sub(r'''['"\\]''', r'\\\g<0>', result) result = re.sub('\n', r'\A ', result) results[match.group(1)] = result text = text[:match.start(0)] + result + text[match.end(0):] if DEBUG: print(_('Replacing templates')) for match in matches: print(_('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0))) print(_('Replace with: %s') % results[match.group(1)]) return text def prince_convert(self): ''' Call the actual Prince command to convert to PDF ''' from os import makedirs from os.path import dirname, join, exists from calibre.ptempfile import PersistentTemporaryFile from calibre.constants import DEBUG from shlex import split as shsplit # All files are relative to the OPF location opf_dir = dirname(self.opf) base_dir = dirname(self.pdf_file) base_dir = join(opf_dir, base_dir) try: makedirs(base_dir) except BaseException: if not exists(base_dir): raise # Create a temporary CSS file with the box contents custom_CSS = PersistentTemporaryFile() custom_CSS.write(unicode(self.css1.toPlainText())) custom_CSS.close() # Create a temporary file with the list of input files file_list = PersistentTemporaryFile() for item in self.oeb.spine: file_list.write(item.href + "\n") file_list.close() # Build the command line command = prefs['prince_exe'] args = ['-v'] if self.prince_file: args.append('-s') args.append(self.prince_file) args.append('-s') args.append(custom_CSS.name) args.append('-l') args.append(file_list.name) args.append('-o') args.append(self.pdf_file) # Additional command-line arguments args.extend(shsplit(self.args.text())) # Hide the convert button and show a busy indicator self.convert.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setRange(0,0) self.progress_bar.setValue(0) self.l.addWidget(self.progress_bar) # Run the command and return the path to the PDF file if DEBUG: print(_('Converting book...')) process = QProcess(self) process.setWorkingDirectory(opf_dir) process.setProcessChannelMode(QProcess.MergedChannels); process.error.connect(self.error) process.finished.connect(self.end) self.process = process if DEBUG: from subprocess import list2cmdline line = list2cmdline([command] + args) print(_('Command line: %s') % line) process.start(command, args) def error(self, rc): ''' Show a message when there is an error in the command :param rc: The error code ''' from calibre.gui2 import error_dialog # Remove the progress bar while the error message is displayed self.progress_bar.hide() self.progress_bar.deleteLater() error_dialog(self, _('Process error'), _('<p>Error code: %s' '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed ' 'and the correct command-line-interface executable is set in the configuration of this plugin, ' 'which is usually:' '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>' ' <li>In Linux: <code>prince</code>' '</ul>') % rc, show=True) self.pdf_file = None self.accept() def end(self, rc): ''' Close and return the filename when the process ends :param rc: The return code (0 if successful) ''' from os.path import join self.prince_log = unicode(self.process.readAllStandardOutput().data()) opf_dir = unicode(self.process.workingDirectory()) if (rc == 0): self.pdf_file = join(opf_dir, self.pdf_file) else: self.pdf_file = None self.accept()