Esempio n. 1
0
    def setup_store_checks(self):
        first_run = self.config.get('first_run', True)

        # Add check boxes for each store so the user
        # can disable searching specific stores on a
        # per search basis.
        existing = {}
        for n in self.store_checks:
            existing[n] = self.store_checks[n].isChecked()

        self.store_checks = {}

        stores_check_widget = QWidget()
        store_list_layout = QGridLayout()
        stores_check_widget.setLayout(store_list_layout)

        icon = QIcon(I('donate.png'))
        for i, x in enumerate(
                sorted(self.gui.istores.keys(), key=lambda x: x.lower())):
            cbox = QCheckBox(x)
            cbox.setChecked(existing.get(x, first_run))
            store_list_layout.addWidget(cbox, i, 0, 1, 1)
            if self.gui.istores[x].base_plugin.affiliate:
                iw = QLabel(self)
                iw.setToolTip('<p>' + _(
                    'Buying from this store supports the calibre developer: %s</p>'
                ) % self.gui.istores[x].base_plugin.author + '</p>')
                iw.setPixmap(icon.pixmap(16, 16))
                store_list_layout.addWidget(iw, i, 1, 1, 1)
            self.store_checks[x] = cbox
        store_list_layout.setRowStretch(store_list_layout.rowCount(), 10)
        self.store_list.setWidget(stores_check_widget)

        self.config['first_run'] = False
Esempio n. 2
0
def test(scale=0.25):
    from qt.core import QLabel, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout
    from calibre.gui2 import Application
    app = Application([])
    mi = Metadata('Unknown', ['Kovid Goyal', 'John & Doe', 'Author'])
    mi.series = 'A series & styles'
    m = QMainWindow()
    sa = QScrollArea(m)
    w = QWidget(m)
    sa.setWidget(w)
    l = QGridLayout(w)
    w.setLayout(l), l.setSpacing(30)
    scale *= w.devicePixelRatioF()
    labels = []
    for r, color in enumerate(sorted(default_color_themes)):
        for c, style in enumerate(sorted(all_styles())):
            mi.series_index = c + 1
            mi.title = 'An algorithmic cover [%s]' % color
            prefs = override_prefs(cprefs, override_color_theme=color, override_style=style)
            scale_cover(prefs, scale)
            img = generate_cover(mi, prefs=prefs, as_qimage=True)
            img.setDevicePixelRatio(w.devicePixelRatioF())
            la = QLabel()
            la.setPixmap(QPixmap.fromImage(img))
            l.addWidget(la, r, c)
            labels.append(la)
    m.setCentralWidget(sa)
    w.resize(w.sizeHint())
    m.show()
    app.exec()
Esempio n. 3
0
class DeviceListGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Show as on device"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.show_recommendations_checkbox = create_checkbox(
                             _("Show recommendations"),
                             _('Kobo shows recommendations on the device.  In some cases these have '
                               'files but in other cases they are just pointers to the web site to buy. '
                               'Enable if you wish to see/delete them.'),
                             device.get_pref('show_recommendations')
                             )

        self.show_archived_books_checkbox = create_checkbox(
                             _("Show archived books"),
                             _('Archived books are listed on the device but need to be downloaded to read.'
                               ' Use this option to show these books and match them with books in the calibre library.'),
                             device.get_pref('show_archived_books')
                             )

        self.show_previews_checkbox = create_checkbox(
                             _('Show previews'),
                             _('Kobo previews are included on the Touch and some other versions'
                               ' by default they are no longer displayed as there is no good reason to '
                               'see them.  Enable if you wish to see/delete them.'),
                             device.get_pref('show_previews')
                             )

        self.options_layout.addWidget(self.show_recommendations_checkbox, 0, 0, 1, 1)
        self.options_layout.addWidget(self.show_archived_books_checkbox,  1, 0, 1, 1)
        self.options_layout.addWidget(self.show_previews_checkbox,        2, 0, 1, 1)

    @property
    def show_recommendations(self):
        return self.show_recommendations_checkbox.isChecked()

    @property
    def show_archived_books(self):
        return self.show_archived_books_checkbox.isChecked()

    @property
    def show_previews(self):
        return self.show_previews_checkbox.isChecked()
Esempio n. 4
0
class AdvancedGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device, _("Advanced options"))
#         self.setTitle(_("Advanced Options"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.support_newer_firmware_checkbox = create_checkbox(
                            _("Attempt to support newer firmware"),
                            _('Kobo routinely updates the firmware and the '
                              'database version. With this option calibre will attempt '
                              'to perform full read-write functionality - Here be Dragons!! '
                              'Enable only if you are comfortable with restoring your kobo '
                              'to factory defaults and testing software. '
                              'This driver supports firmware V2.x.x and DBVersion up to ') + str(
                                  device.supported_dbversion), device.get_pref('support_newer_firmware')
                             )

        self.debugging_title_checkbox = create_checkbox(
                             _("Title to test when debugging"),
                             _('Part of title of a book that can be used when doing some tests for debugging. '
                               'The test is to see if the string is contained in the title of a book. '
                               'The better the match, the less extraneous output.'),
                             device.get_pref('debugging_title')
                             )
        self.debugging_title_label = QLabel(_('Title to test when debugging:'))
        self.debugging_title_edit = QLineEdit(self)
        self.debugging_title_edit.setToolTip(_('Part of title of a book that can be used when doing some tests for debugging. '
                    'The test is to see if the string is contained in the title of a book. '
                    'The better the match, the less extraneous output.'))
        self.debugging_title_edit.setText(device.get_pref('debugging_title'))
        self.debugging_title_label.setBuddy(self.debugging_title_edit)

        self.options_layout.addWidget(self.support_newer_firmware_checkbox,   0, 0, 1, 2)
        self.options_layout.addWidget(self.debugging_title_label,             1, 0, 1, 1)
        self.options_layout.addWidget(self.debugging_title_edit,              1, 1, 1, 1)

    @property
    def support_newer_firmware(self):
        return self.support_newer_firmware_checkbox.isChecked()

    @property
    def debugging_title(self):
        return self.debugging_title_edit.text().strip()
Esempio n. 5
0
class BookUploadsGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Uploading of books"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.modify_css_checkbox = create_checkbox(
                _("Modify CSS"),
                _('This allows addition of user CSS rules and removal of some CSS. '
                'When sending a book, the driver adds the contents of {0} to all stylesheets in the EPUB. '
                'This file is searched for in the root folder of the main memory of the device. '
                'As well as this, if the file contains settings for the "orphans" or "widows", '
                'these are removed for all styles in the original stylesheet.').format(device.KOBO_EXTRA_CSSFILE),
                device.get_pref('modify_css')
                )
        self.override_kobo_replace_existing_checkbox = create_checkbox(
                _("Do not treat replacements as new books"),
                _('When a new book is side-loaded, the Kobo firmware imports details of the book into the internal database. '
                'Even if the book is a replacement for an existing book, the Kobo will remove the book from the database and then treat it as a new book. '
                'This means that the reading status, bookmarks and collections for the book will be lost. '
                'This option overrides firmware behavior and attempts to prevent a book that has been resent from being treated as a new book. '
                'If you prefer to have replacements treated as new books, turn this option off.'
                ),
                device.get_pref('override_kobo_replace_existing')
                )

        self.options_layout.addWidget(self.modify_css_checkbox, 0, 0, 1, 2)
        self.options_layout.addWidget(self.override_kobo_replace_existing_checkbox, 1, 0, 1, 2)

    @property
    def modify_css(self):
        return self.modify_css_checkbox.isChecked()

    @property
    def override_kobo_replace_existing(self):
        return self.override_kobo_replace_existing_checkbox.isChecked()
Esempio n. 6
0
class DownloadStatus(QScrollArea):
    def __init__(self, parent=None):
        QScrollArea.__init__(self, parent)
        self.setWidgetResizable(True)
        self.w = QWidget(self)
        self.l = QGridLayout(self.w)
        self.setWidget(self.w)

    def __call__(self, resources):
        self.url_map = {}
        self.labels = []
        for url in resources:
            p = self.url_map[url] = QProgressBar(self.w)
            p.setRange(0, 0)
            self.l.addWidget(p, self.l.rowCount(), 0)
            la = QLabel('\xa0' + url)
            self.labels.append(la)
            self.l.addWidget(la, self.l.rowCount() - 1, 1)
        self.l.addWidget(QLabel(''))
        self.l.setRowStretch(self.l.rowCount() - 1, 10)

    def progress(self, url, done, total):
        p = self.url_map.get(url)
        if p is not None:
            if total > 0:
                p.setRange(0, total)
                p.setValue(done)
            else:
                p.setRange(0, 0)
Esempio n. 7
0
class CoversGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Upload covers"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.setCheckable(True)
        self.setChecked(device.get_pref('upload_covers'))
        self.setToolTip(wrap_msg(_('Upload cover images from the calibre library when sending books to the device.')))

        self.upload_grayscale_checkbox = create_checkbox(
                             _('Upload black and white covers'),
                             _('Convert covers to grayscale when uploading.'),
                             device.get_pref('upload_grayscale')
                             )

        self.dithered_covers_checkbox = create_checkbox(
                             _('Upload dithered covers'),
                             _('Dither cover images to the appropriate 16c grayscale palette for an eInk screen.'
                               ' This usually ensures greater accuracy and avoids banding, making sleep covers look better.'
                               ' On FW >= 4.11, Nickel itself may sometimes do a decent job of it.'
                               ' Has no effect without "Upload black and white covers"!'),
                             device.get_pref('dithered_covers')
                             )
        # Make it visually depend on B&W being enabled!
        # c.f., https://stackoverflow.com/q/36281103
        self.dithered_covers_checkbox.setEnabled(device.get_pref('upload_grayscale'))
        self.upload_grayscale_checkbox.toggled.connect(self.dithered_covers_checkbox.setEnabled)
        self.upload_grayscale_checkbox.toggled.connect(
            lambda checked: not checked and self.dithered_covers_checkbox.setChecked(False))

        self.keep_cover_aspect_checkbox = create_checkbox(
                             _('Keep cover aspect ratio'),
                             _('When uploading covers, do not change the aspect ratio when resizing for the device.'
                               ' This is for firmware versions 2.3.1 and later.'),
                             device.get_pref('keep_cover_aspect'))

        self.letterbox_fs_covers_checkbox = create_checkbox(
                             _('Letterbox full-screen covers'),
                             _('Do it on our end, instead of letting Nickel handle it.'
                               ' Provides pixel-perfect results on devices where Nickel does not do extra processing.'
                               ' Obviously has no effect without "Keep cover aspect ratio".'
                               ' This is probably undesirable if you disable the "Show book covers full screen"'
                               ' setting on your device.'),
                             device.get_pref('letterbox_fs_covers'))

        self.letterbox_fs_covers_color_button = ColorButton(self.options_layout)
        self.letterbox_fs_covers_color_button.setToolTip(_('Choose the color to use when letterboxing the cover.'
                                                           ' The default color is black (#000000)'
                                                           )
        )
        self.letterbox_fs_covers_color_button.color = device.get_pref('letterbox_fs_covers_color')

        # Make it visually depend on AR being enabled!
        self.letterbox_fs_covers_checkbox.setEnabled(device.get_pref('keep_cover_aspect'))
        self.letterbox_fs_covers_color_button.setEnabled(device.get_pref('keep_cover_aspect') and device.get_pref('letterbox_fs_covers'))
        self.keep_cover_aspect_checkbox.toggled.connect(self.letterbox_fs_covers_checkbox.setEnabled)
        self.keep_cover_aspect_checkbox.toggled.connect(
            lambda checked: not checked and self.letterbox_fs_covers_checkbox.setChecked(False))
        self.letterbox_fs_covers_checkbox.toggled.connect(self.letterbox_fs_covers_color_button.setEnabled)

        self.png_covers_checkbox = create_checkbox(
                             _('Save covers as PNG'),
                             _('Use the PNG image format instead of JPG.'
                               ' Higher quality, especially with "Upload dithered covers" enabled,'
                               ' which will also help generate potentially smaller files.'
                               ' Behavior completely unknown on old (< 3.x) Kobo firmwares,'
                               ' known to behave on FW >= 4.8.'
                               ' Has no effect without "Upload black and white covers"!'),
                             device.get_pref('png_covers'))
        # Make it visually depend on B&W being enabled, to avoid storing ridiculously large color PNGs.
        self.png_covers_checkbox.setEnabled(device.get_pref('upload_grayscale'))
        self.upload_grayscale_checkbox.toggled.connect(self.png_covers_checkbox.setEnabled)
        self.upload_grayscale_checkbox.toggled.connect(
            lambda checked: not checked and self.png_covers_checkbox.setChecked(False))

        self.options_layout.addWidget(self.keep_cover_aspect_checkbox,          0, 0, 1, 1)
        self.options_layout.addWidget(self.letterbox_fs_covers_checkbox,        0, 1, 1, 2)
        self.options_layout.addWidget(self.letterbox_fs_covers_color_button,    1, 1, 1, 1)
        self.options_layout.addWidget(self.upload_grayscale_checkbox,           2, 0, 1, 1)
        self.options_layout.addWidget(self.dithered_covers_checkbox,            2, 1, 1, 2)
        self.options_layout.addWidget(self.png_covers_checkbox,                 3, 1, 1, 2)
        self.options_layout.setColumnStretch(0, 0)
        self.options_layout.setColumnStretch(1, 0)
        self.options_layout.setColumnStretch(2, 1)

    @property
    def upload_covers(self):
        return self.isChecked()

    @property
    def upload_grayscale(self):
        return self.upload_grayscale_checkbox.isChecked()

    @property
    def dithered_covers(self):
        return self.dithered_covers_checkbox.isChecked()

    @property
    def keep_cover_aspect(self):
        return self.keep_cover_aspect_checkbox.isChecked()

    @property
    def letterbox_fs_covers(self):
        return self.letterbox_fs_covers_checkbox.isChecked()

    @property
    def letterbox_fs_covers_color(self):
        return self.letterbox_fs_covers_color_button.color

    @property
    def png_covers(self):
        return self.png_covers_checkbox.isChecked()
Esempio n. 8
0
class CollectionsGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Collections"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.setCheckable(True)
        self.setChecked(device.get_pref('manage_collections'))
        self.setToolTip(wrap_msg(_('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.')))

        self.collections_columns_label = QLabel(_('Collections columns:'))
        self.collections_columns_edit = QLineEdit(self)
        self.collections_columns_edit.setToolTip(_('The Kobo from firmware V2.0.0 supports bookshelves.'
                ' These are created on the Kobo. '
                'Specify a tags type column for automatic management.'))
        self.collections_columns_edit.setText(device.get_pref('collections_columns'))

        self.create_collections_checkbox = create_checkbox(
                         _("Create collections"),
                         _('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.'),
                         device.get_pref('create_collections')
                         )
        self.delete_empty_collections_checkbox = create_checkbox(
                         _('Delete empty bookshelves'),
                         _('Delete any empty bookshelves from the Kobo when syncing is finished. This is only for firmware V2.0.0 or later.'),
                         device.get_pref('delete_empty_collections')
                         )

        self.ignore_collections_names_label = QLabel(_('Ignore collections:'))
        self.ignore_collections_names_edit = QLineEdit(self)
        self.ignore_collections_names_edit.setToolTip(_('List the names of collections to be ignored by '
                'the collection management. The collections listed '
                'will not be changed. Names are separated by commas.'))
        self.ignore_collections_names_edit.setText(device.get_pref('ignore_collections_names'))

        self.options_layout.addWidget(self.collections_columns_label,         1, 0, 1, 1)
        self.options_layout.addWidget(self.collections_columns_edit,          1, 1, 1, 1)
        self.options_layout.addWidget(self.create_collections_checkbox,       2, 0, 1, 2)
        self.options_layout.addWidget(self.delete_empty_collections_checkbox, 3, 0, 1, 2)
        self.options_layout.addWidget(self.ignore_collections_names_label,    4, 0, 1, 1)
        self.options_layout.addWidget(self.ignore_collections_names_edit,     4, 1, 1, 1)

    @property
    def manage_collections(self):
        return self.isChecked()

    @property
    def collections_columns(self):
        return self.collections_columns_edit.text().strip()

    @property
    def create_collections(self):
        return self.create_collections_checkbox.isChecked()

    @property
    def delete_empty_collections(self):
        return self.delete_empty_collections_checkbox.isChecked()

    @property
    def ignore_collections_names(self):
        return self.ignore_collections_names_edit.text().strip()
Esempio n. 9
0
class CollectionsGroupBox(DeviceOptionsGroupBox):
    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Collections"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.setCheckable(True)
        self.setChecked(device.get_pref('manage_collections'))
        self.setToolTip(
            wrap_msg(
                _('Create new bookshelves on the Kobo if they do not exist. This is only for firmware V2.0.0 or later.'
                  )))

        self.use_collections_columns_checkbox = create_checkbox(
            _("Collections columns:"),
            _('Use a column to generate collections.'),
            device.get_pref('use_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.use_collections_template_checkbox = create_checkbox(
            _("Collections template:"),
            _('Use a template to generate collections.'),
            device.get_pref('use_collections_template'))
        self.collections_template_edit = TemplateConfig(
            device.get_pref('collections_template'),
            tooltip=
            _("Enter a template to generate collections."
              " The result of the template will be combined with the values from Collections column."
              " The template should return a list of collection names separated by ':@:' (without quotes)."
              ))

        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.use_collections_columns_checkbox, 1,
                                      0, 1, 1)
        self.options_layout.addWidget(self.collections_columns_edit, 1, 1, 1,
                                      1)
        self.options_layout.addWidget(self.use_collections_template_checkbox,
                                      2, 0, 1, 1)
        self.options_layout.addWidget(self.collections_template_edit, 2, 1, 1,
                                      1)
        self.options_layout.addWidget(self.create_collections_checkbox, 3, 0,
                                      1, 2)
        self.options_layout.addWidget(self.delete_empty_collections_checkbox,
                                      4, 0, 1, 2)
        self.options_layout.addWidget(self.ignore_collections_names_label, 5,
                                      0, 1, 1)
        self.options_layout.addWidget(self.ignore_collections_names_edit, 5, 1,
                                      1, 1)

        self.use_collections_columns_checkbox.clicked.connect(
            self.use_collections_columns_checkbox_clicked)
        self.use_collections_template_checkbox.clicked.connect(
            self.use_collections_template_checkbox_clicked)
        self.use_collections_columns_checkbox_clicked(
            device.get_pref('use_collections_columns'))
        self.use_collections_template_checkbox_clicked(
            device.get_pref('use_collections_template'))

    def use_collections_columns_checkbox_clicked(self, checked):
        self.collections_columns_edit.setEnabled(checked)

    def use_collections_template_checkbox_clicked(self, checked):
        self.collections_template_edit.setEnabled(checked)

    @property
    def manage_collections(self):
        return self.isChecked()

    @property
    def use_collections_columns(self):
        return self.use_collections_columns_checkbox.isChecked()

    @property
    def collections_columns(self):
        return self.collections_columns_edit.text().strip()

    @property
    def use_collections_template(self):
        return self.use_collections_template_checkbox.isChecked()

    @property
    def collections_template(self):
        return self.collections_template_edit.template

    @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 AddEmptyBookDialog(QDialog):
    def __init__(self,
                 parent,
                 db,
                 author,
                 series=None,
                 title=None,
                 dup_title=None):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('How many empty books?'))

        self._layout = QGridLayout(self)
        self.setLayout(self._layout)

        self.qty_label = QLabel(_('How many empty books should be added?'))
        self._layout.addWidget(self.qty_label, 0, 0, 1, 2)

        self.qty_spinbox = QSpinBox(self)
        self.qty_spinbox.setRange(1, 10000)
        self.qty_spinbox.setValue(1)
        self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)

        self.author_label = QLabel(_('Set the author of the new books to:'))
        self._layout.addWidget(self.author_label, 2, 0, 1, 2)

        self.authors_combo = EditWithComplete(self)
        self.authors_combo.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
        self.authors_combo.setEditable(True)
        self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
        self.initialize_authors(db, author)

        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('trash.png')))
        self.clear_button.setToolTip(_('Reset author to Unknown'))
        self.clear_button.clicked.connect(self.reset_author)
        self._layout.addWidget(self.clear_button, 3, 1, 1, 1)

        self.series_label = QLabel(_('Set the series of the new books to:'))
        self._layout.addWidget(self.series_label, 4, 0, 1, 2)

        self.series_combo = EditWithComplete(self)
        self.series_combo.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setEditable(True)
        self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
        self.initialize_series(db, series)

        self.sclear_button = QToolButton(self)
        self.sclear_button.setIcon(QIcon(I('trash.png')))
        self.sclear_button.setToolTip(_('Reset series'))
        self.sclear_button.clicked.connect(self.reset_series)
        self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)

        self.title_label = QLabel(_('Set the title of the new books to:'))
        self._layout.addWidget(self.title_label, 6, 0, 1, 2)

        self.title_edit = QLineEdit(self)
        self.title_edit.setText(title or '')
        self._layout.addWidget(self.title_edit, 7, 0, 1, 1)

        self.tclear_button = QToolButton(self)
        self.tclear_button.setIcon(QIcon(I('trash.png')))
        self.tclear_button.setToolTip(_('Reset title'))
        self.tclear_button.clicked.connect(self.title_edit.clear)
        self._layout.addWidget(self.tclear_button, 7, 1, 1, 1)

        self.format_label = QLabel(_('Also create an empty e-book in format:'))
        self._layout.addWidget(self.format_label, 8, 0, 1, 2)
        c = self.format_value = QComboBox(self)
        from calibre.ebooks.oeb.polish.create import valid_empty_formats
        possible_formats = [''] + sorted(x.upper()
                                         for x in valid_empty_formats)
        c.addItems(possible_formats)
        c.setToolTip(
            _('Also create an empty book format file that you can subsequently edit'
              ))
        if gprefs.get('create_empty_epub_file', False):
            # Migration of the check box
            gprefs.set('create_empty_format_file', 'epub')
            del gprefs['create_empty_epub_file']
        use_format = gprefs.get('create_empty_format_file', '').upper()
        try:
            c.setCurrentIndex(possible_formats.index(use_format))
        except Exception:
            pass
        self._layout.addWidget(c, 9, 0, 1, 1)

        self.copy_formats = cf = QCheckBox(
            _('Also copy book &formats when duplicating a book'), self)
        cf.setToolTip(
            _('Also copy all e-book files into the newly created duplicate'
              ' books.'))
        cf.setChecked(gprefs.get('create_empty_copy_dup_formats', False))
        self._layout.addWidget(cf, 10, 0, 1, -1)

        button_box = self.bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 11, 0, 1, -1)
        if dup_title:
            self.dup_button = b = button_box.addButton(
                _('&Duplicate current book'),
                QDialogButtonBox.ButtonRole.ActionRole)
            b.clicked.connect(self.do_duplicate_book)
            b.setIcon(QIcon(I('edit-copy.png')))
            b.setToolTip(
                _('Make the new empty book records exact duplicates\n'
                  'of the current book "%s", with all metadata identical') %
                dup_title)
        self.resize(self.sizeHint())
        self.duplicate_current_book = False

    def do_duplicate_book(self):
        self.duplicate_current_book = True
        self.accept()

    def accept(self):
        self.save_settings()
        return QDialog.accept(self)

    def save_settings(self):
        gprefs['create_empty_format_file'] = self.format_value.currentText(
        ).lower()
        gprefs['create_empty_copy_dup_formats'] = self.copy_formats.isChecked()

    def reject(self):
        self.save_settings()
        return QDialog.reject(self)

    def reset_author(self, *args):
        self.authors_combo.setEditText(_('Unknown'))

    def reset_series(self):
        self.series_combo.setEditText('')

    def initialize_authors(self, db, author):
        au = author
        if not au:
            au = _('Unknown')
        self.authors_combo.show_initial_value(au.replace('|', ','))

        self.authors_combo.set_separator('&')
        self.authors_combo.set_space_before_sep(True)
        self.authors_combo.set_add_separator(
            tweaks['authors_completer_append_separator'])
        self.authors_combo.update_items_cache(db.all_author_names())

    def initialize_series(self, db, series):
        self.series_combo.show_initial_value(series or '')
        self.series_combo.update_items_cache(db.all_series_names())
        self.series_combo.set_separator(None)

    @property
    def qty_to_add(self):
        return self.qty_spinbox.value()

    @property
    def selected_authors(self):
        return string_to_authors(unicode_type(self.authors_combo.text()))

    @property
    def selected_series(self):
        return unicode_type(self.series_combo.text())

    @property
    def selected_title(self):
        return self.title_edit.text().strip()
Esempio n. 11
0
class ConfigWidget(QWidget):
    def __init__(self, plugin):
        QWidget.__init__(self)
        self.plugin = plugin

        self.overl = l = QVBoxLayout(self)
        self.gb = QGroupBox(_('Metadata fields to download'), self)
        if plugin.config_help_message:
            self.pchm = QLabel(plugin.config_help_message)
            self.pchm.setWordWrap(True)
            self.pchm.setOpenExternalLinks(True)
            l.addWidget(self.pchm, 10)
        l.addWidget(self.gb)
        self.gb.l = g = QVBoxLayout(self.gb)
        g.setContentsMargins(0, 0, 0, 0)
        self.fields_view = v = FieldsList(self)
        g.addWidget(v)
        v.setFlow(QListView.Flow.LeftToRight)
        v.setWrapping(True)
        v.setResizeMode(QListView.ResizeMode.Adjust)
        self.fields_model = FieldsModel(self.plugin)
        self.fields_model.initialize()
        v.setModel(self.fields_model)
        self.memory = []
        self.widgets = []
        self.l = QGridLayout()
        self.l.setContentsMargins(0, 0, 0, 0)
        l.addLayout(self.l, 100)
        for opt in plugin.options:
            self.create_widgets(opt)

    def create_widgets(self, opt):
        val = self.plugin.prefs[opt.name]
        if opt.type == 'number':
            c = QSpinBox if isinstance(opt.default,
                                       numbers.Integral) 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(iteritems(opt.choices))
            items.sort(key=lambda k_v: sort_key(k_v[1]))
            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)

    def commit(self):
        self.fields_model.commit()
        for w in self.widgets:
            if isinstance(w, (QSpinBox, QDoubleSpinBox)):
                val = w.value()
            elif isinstance(w, QLineEdit):
                val = str(w.text())
            elif isinstance(w, QCheckBox):
                val = w.isChecked()
            elif isinstance(w, QComboBox):
                idx = w.currentIndex()
                val = str(w.itemData(idx) or '')
            self.plugin.prefs[w.opt.name] = val
Esempio n. 12
0
class ExtraCustomization(DeviceConfigTab):  # {{{
    def __init__(self, extra_customization_message,
                 extra_customization_choices, device_settings):
        super(ExtraCustomization, self).__init__()

        debug_print(
            "ExtraCustomization.__init__ - extra_customization_message=",
            extra_customization_message)
        debug_print(
            "ExtraCustomization.__init__ - extra_customization_choices=",
            extra_customization_choices)
        debug_print(
            "ExtraCustomization.__init__ - device_settings.extra_customization=",
            device_settings.extra_customization)
        debug_print("ExtraCustomization.__init__ - device_settings=",
                    device_settings)
        self.extra_customization_message = extra_customization_message

        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        options_group = QGroupBox(_("Extra driver customization options"),
                                  self)
        self.l.addWidget(options_group)
        self.extra_layout = QGridLayout()
        self.extra_layout.setObjectName("extra_layout")
        options_group.setLayout(self.extra_layout)

        if extra_customization_message:
            extra_customization_choices = extra_customization_choices or {}

            def parse_msg(m):
                msg, _, tt = m.partition(':::') if m else ('', '', '')
                return msg.strip(), textwrap.fill(tt.strip(), 100)

            if isinstance(extra_customization_message, list):
                self.opt_extra_customization = []
                if len(extra_customization_message) > 6:
                    row_func = lambda x, y: ((x // 2) * 2) + y
                    col_func = lambda x: x % 2
                else:
                    row_func = lambda x, y: x * 2 + y
                    col_func = lambda x: 0

                for i, m in enumerate(extra_customization_message):
                    label_text, tt = parse_msg(m)
                    if not label_text:
                        self.opt_extra_customization.append(None)
                        continue
                    if isinstance(device_settings.extra_customization[i],
                                  bool):
                        self.opt_extra_customization.append(
                            QCheckBox(label_text))
                        self.opt_extra_customization[-1].setToolTip(tt)
                        self.opt_extra_customization[i].setChecked(
                            bool(device_settings.extra_customization[i]))
                    elif i in extra_customization_choices:
                        cb = QComboBox(self)
                        self.opt_extra_customization.append(cb)
                        l = QLabel(label_text)
                        l.setToolTip(tt), cb.setToolTip(tt), l.setBuddy(
                            cb), cb.setToolTip(tt)
                        for li in sorted(extra_customization_choices[i]):
                            self.opt_extra_customization[i].addItem(li)
                        cb.setCurrentIndex(
                            max(
                                0,
                                cb.findText(
                                    device_settings.extra_customization[i])))
                    else:
                        self.opt_extra_customization.append(QLineEdit(self))
                        l = QLabel(label_text)
                        l.setToolTip(tt)
                        self.opt_extra_customization[i].setToolTip(tt)
                        l.setBuddy(self.opt_extra_customization[i])
                        l.setWordWrap(True)
                        self.opt_extra_customization[i].setText(
                            device_settings.extra_customization[i])
                        self.opt_extra_customization[i].setCursorPosition(0)
                        self.extra_layout.addWidget(l, row_func(i + 2, 0),
                                                    col_func(i))
                    self.extra_layout.addWidget(
                        self.opt_extra_customization[i], row_func(i + 2, 1),
                        col_func(i))
                spacerItem1 = QSpacerItem(10, 10, QSizePolicy.Policy.Minimum,
                                          QSizePolicy.Policy.Expanding)
                self.extra_layout.addItem(spacerItem1, row_func(i + 2 + 2, 1),
                                          0, 1, 2)
                self.extra_layout.setRowStretch(row_func(i + 2 + 2, 1), 2)
            else:
                self.opt_extra_customization = QLineEdit()
                label_text, tt = parse_msg(extra_customization_message)
                l = QLabel(label_text)
                l.setToolTip(tt)
                l.setBuddy(self.opt_extra_customization)
                l.setWordWrap(True)
                if device_settings.extra_customization:
                    self.opt_extra_customization.setText(
                        device_settings.extra_customization)
                    self.opt_extra_customization.setCursorPosition(0)
                self.opt_extra_customization.setCursorPosition(0)
                self.extra_layout.addWidget(l, 0, 0)
                self.extra_layout.addWidget(self.opt_extra_customization, 1, 0)

    def extra_customization(self):
        ec = []
        if self.extra_customization_message:
            if isinstance(self.extra_customization_message, list):
                for i in range(0, len(self.extra_customization_message)):
                    if self.opt_extra_customization[i] is None:
                        ec.append(None)
                        continue
                    if hasattr(self.opt_extra_customization[i], 'isChecked'):
                        ec.append(self.opt_extra_customization[i].isChecked())
                    elif hasattr(self.opt_extra_customization[i],
                                 'currentText'):
                        ec.append(
                            unicode_type(self.opt_extra_customization[i].
                                         currentText()).strip())
                    else:
                        ec.append(
                            unicode_type(self.opt_extra_customization[i].text(
                            )).strip())
            else:
                ec = unicode_type(self.opt_extra_customization.text()).strip()
                if not ec:
                    ec = None

        return ec

    @property
    def has_extra_customizations(self):
        debug_print(
            "ExtraCustomization::has_extra_customizations - self.extra_customization_message",
            self.extra_customization_message)
        return self.extra_customization_message and len(
            self.extra_customization_message) > 0
Esempio n. 13
0
class CheckLibraryDialog(QDialog):

    is_deletable = 1
    is_fixable = 2

    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.new_api.pref('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.new_api.pref('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',
                                 str(self.ext_ignores.text()))
        self.db.new_api.set_pref('check_library_ignore_names',
                                 str(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(str(self.name_ignores.text())),
                             self.box_to_list(str(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.setData(1, Qt.ItemDataRole.UserRole, self.is_fixable)
                tl.setText(1, _('(fixable)'))
                tl.setFlags(Qt.ItemFlag.ItemIsEnabled
                            | Qt.ItemFlag.ItemIsUserCheckable)
                tl.setCheckState(1, False)
            else:
                tl.setData(1, Qt.ItemDataRole.UserRole, self.is_deletable)
                tl.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable)
                tl.setText(1, _('(deletable)'))
                tl.setFlags(Qt.ItemFlag.ItemIsEnabled
                            | Qt.ItemFlag.ItemIsUserCheckable)
                tl.setCheckState(1, False)
            if attr == 'extra_covers':
                tl.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable)
                tl.setText(2, _('(deletable)'))
                tl.setFlags(Qt.ItemFlag.ItemIsEnabled
                            | Qt.ItemFlag.ItemIsUserCheckable)
                tl.setCheckState(2, False)
            self.top_level_items[attr] = tl

            for problem in list_:
                it = Item()
                if checkable:
                    it.setFlags(Qt.ItemFlag.ItemIsEnabled
                                | Qt.ItemFlag.ItemIsUserCheckable)
                    it.setCheckState(2, False)
                    it.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable)
                else:
                    it.setFlags(Qt.ItemFlag.ItemIsEnabled)
                it.setText(0, problem[0])
                it.setData(0, Qt.ItemDataRole.UserRole, problem[2])
                it.setText(2, 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(3)
        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):
        def set_delete_boxes(node, col, to_what):
            self.log.blockSignals(True)
            if col:
                node.setCheckState(col, to_what)
            for i in range(0, node.childCount()):
                node.child(i).setCheckState(2, to_what)
            self.log.blockSignals(False)

        def is_child_delete_checked(node):
            checked = False
            all_checked = True
            for i in range(0, node.childCount()):
                c = node.child(i).checkState(2)
                checked = checked or c == Qt.CheckState.Checked
                all_checked = all_checked and c == Qt.CheckState.Checked
            return (checked, all_checked)

        def any_child_delete_checked():
            for parent in self.top_level_items.values():
                (c, _) = is_child_delete_checked(parent)
                if c:
                    return True
            return False

        def any_fix_checked():
            for parent in self.top_level_items.values():
                if (parent.data(1, Qt.ItemDataRole.UserRole) == self.is_fixable
                        and parent.checkState(1) == Qt.CheckState.Checked):
                    return True
            return False

        if item in self.top_level_items.values():
            if item.childCount() > 0:
                if item.data(1, Qt.ItemDataRole.UserRole
                             ) == self.is_fixable and column == 1:
                    if item.data(
                            2, Qt.ItemDataRole.UserRole) == self.is_deletable:
                        set_delete_boxes(item, 2, False)
                else:
                    set_delete_boxes(item, column, item.checkState(column))
                    if column == 2:
                        self.log.blockSignals(True)
                        item.setCheckState(1, False)
                        self.log.blockSignals(False)
            else:
                item.setCheckState(column, Qt.CheckState.Unchecked)
        else:
            for parent in self.top_level_items.values():
                if parent.data(2,
                               Qt.ItemDataRole.UserRole) == self.is_deletable:
                    (child_chkd, all_chkd) = is_child_delete_checked(parent)
                    if all_chkd and child_chkd:
                        check_state = Qt.CheckState.Checked
                    elif child_chkd:
                        check_state = Qt.CheckState.PartiallyChecked
                    else:
                        check_state = Qt.CheckState.Unchecked
                    self.log.blockSignals(True)
                    if parent.data(
                            1, Qt.ItemDataRole.UserRole) == self.is_fixable:
                        parent.setCheckState(2, check_state)
                    else:
                        parent.setCheckState(1, check_state)
                    if child_chkd and parent.data(
                            1, Qt.ItemDataRole.UserRole) == self.is_fixable:
                        parent.setCheckState(1, Qt.CheckState.Unchecked)
                    self.log.blockSignals(False)
        self.delete_button.setEnabled(any_child_delete_checked())
        self.fix_button.setEnabled(any_fix_checked())

    def mark_for_fix(self):
        for it in self.top_level_items.values():
            if (it.flags() & Qt.ItemFlag.ItemIsUserCheckable
                    and it.data(1, Qt.ItemDataRole.UserRole) == self.is_fixable
                    and it.childCount() > 0):
                it.setCheckState(1, Qt.CheckState.Checked)

    def mark_for_delete(self):
        for it in self.all_items:
            if (it.flags() & Qt.ItemFlag.ItemIsUserCheckable and it.data(
                    2, Qt.ItemDataRole.UserRole) == self.is_deletable):
                it.setCheckState(2, Qt.CheckState.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(2) == Qt.CheckState.Checked:
                try:
                    p = os.path.join(self.db.library_path, str(it.text(2)))
                    if os.path.isdir(p):
                        delete_tree(p)
                    else:
                        delete_file(p)
                except:
                    prints('failed to delete',
                           os.path.join(self.db.library_path, str(it.text(2))))
        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.ItemDataRole.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.ItemDataRole.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.ItemDataRole.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)
Esempio n. 14
0
class MetadataGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super().__init__(parent, device)
        self.setTitle(_("Update metadata on the device"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.setCheckable(True)
        self.setChecked(device.get_pref('update_device_metadata'))
        self.setToolTip(wrap_msg(_('Update the metadata on the device when it is connected. '
                               'Be careful when doing this as it will take time and could make the initial connection take a long time.')))

        self.update_series_checkbox = create_checkbox(
                             _("Set series information"),
                             _('The book lists on the Kobo devices can display series information. '
                               'This is not read by the device from the sideloaded books. '
                               'Series information can only be added to the device after the book has been processed by the device. '
                               'Enable if you wish to set series information.'),
                             device.get_pref('update_series')
                             )
        self.update_core_metadata_checkbox = create_checkbox(
                             _("Update metadata on Book Details pages"),
                             _('This will update the metadata in the device database when the device is connected. '
                               'The metadata updated is displayed on the device in the library and the book details page. '
                               'This is the title, authors, comments/synopsis, series name and number, publisher and published Date, ISBN and language. '
                               'If a metadata plugboard exists for the device and book format, this will be used to set the metadata.'
                               ),
                             device.get_pref('update_core_metadata')
                             )

        self.update_purchased_kepubs_checkbox = create_checkbox(
                             _("Update purchased books"),
                             _('Update books purchased from Kobo and downloaded to the device.'
                               ),
                             device.get_pref('update_purchased_kepubs')
                             )
        self.update_subtitle_checkbox = create_checkbox(
                             _("Subtitle"),
                             _('Update the subtitle on the device using a template.'),
                             device.get_pref('update_subtitle')
                             )
        self.subtitle_template_edit = TemplateConfig(
                            device.get_pref('subtitle_template'),
                            tooltip=_("Enter a template to use to set the subtitle. "
                                      "If the template is empty, the subtitle will be cleared."
                                      )
                            )

        self.options_layout.addWidget(self.update_series_checkbox, 0, 0, 1, 2)
        self.options_layout.addWidget(self.update_core_metadata_checkbox, 1, 0, 1, 2)
        self.options_layout.addWidget(self.update_subtitle_checkbox, 2, 0, 1, 1)
        self.options_layout.addWidget(self.subtitle_template_edit, 2, 1, 1, 1)
        self.options_layout.addWidget(self.update_purchased_kepubs_checkbox, 3, 0, 1, 2)

        self.update_core_metadata_checkbox.clicked.connect(self.update_core_metadata_checkbox_clicked)
        self.update_subtitle_checkbox.clicked.connect(self.update_subtitle_checkbox_clicked)
        self.update_core_metadata_checkbox_clicked(device.get_pref('update_core_metadata'))
        self.update_subtitle_checkbox_clicked(device.get_pref('update_subtitle'))

    def update_core_metadata_checkbox_clicked(self, checked):
        self.update_series_checkbox.setEnabled(not checked)
        self.subtitle_template_edit.setEnabled(checked)
        self.update_subtitle_checkbox.setEnabled(checked)
        self.update_subtitle_checkbox_clicked(self.update_subtitle)
        self.update_purchased_kepubs_checkbox.setEnabled(checked)

    def update_subtitle_checkbox_clicked(self, checked):
        self.subtitle_template_edit.setEnabled(checked and self.update_core_metadata)

    def edit_template(self):
        t = TemplateDialog(self, self.template)
        t.setWindowTitle(_('Edit template'))
        if t.exec():
            self.t.setText(t.rule[1])

    def validate(self):
        if self.update_subtitle and not self.subtitle_template_edit.validate():
            return False
        return True

    @property
    def update_series(self):
        return self.update_series_checkbox.isChecked()

    @property
    def update_core_metadata(self):
        return self.update_core_metadata_checkbox.isChecked()

    @property
    def update_purchased_kepubs(self):
        return self.update_purchased_kepubs_checkbox.isChecked()

    @property
    def update_device_metadata(self):
        return self.isChecked()

    @property
    def subtitle_template(self):
        return self.subtitle_template_edit.template

    @property
    def update_subtitle(self):
        return self.update_subtitle_checkbox.isChecked()
Esempio n. 15
0
    def __init__(self, parent, db, key):
        QDialog.__init__(self, parent)

        self.setWindowTitle(_('Edit permissible values for {0}').format(key))
        self.db = db
        l = QGridLayout()

        bbox = QVBoxLayout()
        bbox.addStretch(10)
        self.del_button = QToolButton()
        self.del_button.setIcon(QIcon(I('trash.png')))
        self.del_button.setToolTip(_('Remove the currently selected value'))
        self.ins_button = QToolButton()
        self.ins_button.setIcon(QIcon(I('plus.png')))
        self.ins_button.setToolTip(_('Add a new permissible value'))
        self.move_up_button = QToolButton()
        self.move_up_button.setIcon(QIcon(I('arrow-up.png')))
        self.move_down_button = QToolButton()
        self.move_down_button.setIcon(QIcon(I('arrow-down.png')))
        bbox.addWidget(self.del_button)
        bbox.addStretch(1)
        bbox.addWidget(self.ins_button)
        bbox.addStretch(1)
        bbox.addWidget(self.move_up_button)
        bbox.addStretch(1)
        bbox.addWidget(self.move_down_button)
        bbox.addStretch(10)
        l.addItem(bbox, 0, 0)

        self.del_button.clicked.connect(self.del_line)

        self.all_colors = {unicode_type(s) for s in list(QColor.colorNames())}

        tl = QVBoxLayout()
        l.addItem(tl, 0, 1)
        self.table = t = QTableWidget(parent)
        t.setColumnCount(2)
        t.setRowCount(1)
        t.setHorizontalHeaderLabels([_('Value'), _('Color')])
        t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        tl.addWidget(t)

        self.fm = fm = db.field_metadata[key]
        permitted_values = fm.get('display', {}).get('enum_values', '')
        colors = fm.get('display', {}).get('enum_colors', '')
        t.setRowCount(len(permitted_values))
        for i, v in enumerate(permitted_values):
            t.setItem(i, 0, QTableWidgetItem(v))
            c = self.make_color_combobox(i, -1)
            if colors:
                c.setCurrentIndex(c.findText(colors[i]))
            else:
                c.setCurrentIndex(0)

        t.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeMode.Stretch)

        self.setLayout(l)

        self.bb = bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        l.addWidget(bb, 1, 0, 1, 2)

        self.ins_button.clicked.connect(self.ins_button_clicked)
        self.move_down_button.clicked.connect(self.move_down_clicked)
        self.move_up_button.clicked.connect(self.move_up_clicked)
        geom = gprefs.get('enum-values-edit-geometry')
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)
Esempio n. 16
0
class UpdateNotification(QDialog):
    def __init__(self, calibre_version, plugin_updates, parent=None):
        QDialog.__init__(self, parent)
        self.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
        self.resize(400, 250)
        self.l = QGridLayout()
        self.setLayout(self.l)
        self.logo = QLabel()
        self.logo.setMaximumWidth(110)
        self.logo.setPixmap(QIcon(I('lt.png')).pixmap(100, 100))
        ver = calibre_version
        if ver.endswith('.0'):
            ver = ver[:-2]
        self.label = QLabel(
            '<p>' +
            _('New version <b>{ver}</b> of {app} is available for download. '
              'See the <a href="{url}">new features</a>.').format(
                  url=localize_website_link(
                      'https://calibre-ebook.com/whats-new'),
                  app=__appname__,
                  ver=ver))
        self.label.setOpenExternalLinks(True)
        self.label.setWordWrap(True)
        self.setWindowTitle(_('Update available!'))
        self.setWindowIcon(QIcon(I('lt.png')))
        self.l.addWidget(self.logo, 0, 0)
        self.l.addWidget(self.label, 0, 1)
        self.cb = QCheckBox(_('Show this notification for future updates'),
                            self)
        self.l.addWidget(self.cb, 1, 0, 1, -1)
        self.cb.setChecked(config.get('new_version_notification'))
        self.cb.stateChanged.connect(self.show_future)
        self.bb = QDialogButtonBox(self)
        b = self.bb.addButton(_('&Get update'),
                              QDialogButtonBox.ButtonRole.AcceptRole)
        b.setDefault(True)
        b.setIcon(QIcon(I('arrow-down.png')))
        if plugin_updates > 0:
            b = self.bb.addButton(_('Update &plugins'),
                                  QDialogButtonBox.ButtonRole.ActionRole)
            b.setIcon(QIcon(I('plugins/plugin_updater.png')))
            b.clicked.connect(self.get_plugins,
                              type=Qt.ConnectionType.QueuedConnection)
        self.bb.addButton(QDialogButtonBox.StandardButton.Cancel)
        self.l.addWidget(self.bb, 2, 0, 1, -1)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        save_version_notified(calibre_version)

    def get_plugins(self):
        from calibre.gui2.dialogs.plugin_updater import (
            PluginUpdaterDialog, FILTER_UPDATE_AVAILABLE)
        d = PluginUpdaterDialog(self.parent(),
                                initial_filter=FILTER_UPDATE_AVAILABLE)
        d.exec()
        if d.do_restart:
            QDialog.accept(self)
            from calibre.gui2.ui import get_gui
            gui = get_gui()
            if gui is not None:
                gui.quit(restart=True)

    def show_future(self, *args):
        config.set('new_version_notification', bool(self.cb.isChecked()))

    def accept(self):
        open_url(QUrl(get_download_url()))

        QDialog.accept(self)
Esempio n. 17
0
class Config(QDialog):
    '''
    Configuration dialog for single book conversion. If accepted, has the
    following important attributes

    output_format - Output format (without a leading .)
    input_format  - Input format (without a leading .)
    opf_path - Path to OPF file with user specified metadata
    cover_path - Path to user specified cover (can be None)
    recommendations - A pickled list of 3 tuples in the same format as the
    recommendations member of the Input/Output plugins.
    '''
    def __init__(self,
                 parent,
                 db,
                 book_id,
                 preferred_input_format=None,
                 preferred_output_format=None):
        QDialog.__init__(self, parent)
        self.widgets = []
        self.setupUi()
        self.opt_individual_saved_settings.setVisible(False)
        self.db, self.book_id = db, book_id

        self.setup_input_output_formats(self.db, self.book_id,
                                        preferred_input_format,
                                        preferred_output_format)
        self.setup_pipeline()

        self.input_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.output_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.groups.setSpacing(5)
        self.groups.entered[(QModelIndex)].connect(self.show_group_help)
        rb = self.buttonBox.button(
            QDialogButtonBox.StandardButton.RestoreDefaults)
        rb.setText(_('Restore &defaults'))
        rb.setIcon(QIcon(I('clear_left.png')))
        rb.clicked.connect(self.restore_defaults)
        self.groups.setMouseTracking(True)
        geom = gprefs.get('convert_single_dialog_geom', None)
        if geom:
            QApplication.instance().safe_restore_geometry(self, geom)
        else:
            self.resize(self.sizeHint())

    def current_group_changed(self, cur, prev):
        self.show_pane(cur)

    def setupUi(self):
        self.setObjectName("Dialog")
        self.resize(1024, 700)
        self.setWindowIcon(QIcon(I('convert.png')))
        self.gridLayout = QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.input_label = QLabel(self)
        self.input_label.setObjectName("input_label")
        self.horizontalLayout.addWidget(self.input_label)
        self.input_formats = QComboBox(self)
        self.input_formats.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
        self.input_formats.setMinimumContentsLength(5)
        self.input_formats.setObjectName("input_formats")
        self.horizontalLayout.addWidget(self.input_formats)
        self.opt_individual_saved_settings = QCheckBox(self)
        self.opt_individual_saved_settings.setObjectName(
            "opt_individual_saved_settings")
        self.horizontalLayout.addWidget(self.opt_individual_saved_settings)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding,
                                 QSizePolicy.Policy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.label_2 = QLabel(self)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.output_formats = QComboBox(self)
        self.output_formats.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
        self.output_formats.setMinimumContentsLength(5)
        self.output_formats.setObjectName("output_formats")
        self.horizontalLayout.addWidget(self.output_formats)
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2)
        self.groups = QListView(self)
        sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding,
                                 QSizePolicy.Policy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.groups.sizePolicy().hasHeightForWidth())
        self.groups.setSizePolicy(sizePolicy)
        self.groups.setTabKeyNavigation(True)
        self.groups.setIconSize(QSize(48, 48))
        self.groups.setWordWrap(True)
        self.groups.setObjectName("groups")
        self.gridLayout.addWidget(self.groups, 1, 0, 3, 1)
        self.scrollArea = QScrollArea(self)
        sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding,
                                 QSizePolicy.Policy.Expanding)
        sizePolicy.setHorizontalStretch(4)
        sizePolicy.setVerticalStretch(10)
        sizePolicy.setHeightForWidth(
            self.scrollArea.sizePolicy().hasHeightForWidth())
        self.scrollArea.setSizePolicy(sizePolicy)
        self.scrollArea.setFrameShape(QFrame.Shape.NoFrame)
        self.scrollArea.setLineWidth(0)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.page = QWidget()
        self.page.setObjectName("page")
        self.gridLayout.addWidget(self.scrollArea, 1, 1, 1, 1)
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
        self.buttonBox.setStandardButtons(
            QDialogButtonBox.StandardButton.Cancel
            | QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.RestoreDefaults)
        self.buttonBox.setObjectName("buttonBox")
        self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1)
        self.help = QTextEdit(self)
        self.help.setReadOnly(True)
        sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding,
                                 QSizePolicy.Policy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.help.sizePolicy().hasHeightForWidth())
        self.help.setSizePolicy(sizePolicy)
        self.help.setMaximumHeight(80)
        self.help.setObjectName("help")
        self.gridLayout.addWidget(self.help, 2, 1, 1, 1)
        self.input_label.setBuddy(self.input_formats)
        self.label_2.setBuddy(self.output_formats)
        self.input_label.setText(_("&Input format:"))
        self.opt_individual_saved_settings.setText(
            _("Use &saved conversion settings for individual books"))
        self.label_2.setText(_("&Output format:"))

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def sizeHint(self):
        geom = self.screen().availableSize()
        nh, nw = max(300, geom.height() - 100), max(400, geom.width() - 70)
        return QSize(nw, nh)

    def restore_defaults(self):
        delete_specifics(self.db, self.book_id)
        self.setup_pipeline()

    @property
    def input_format(self):
        return str(self.input_formats.currentText()).lower()

    @property
    def output_format(self):
        return str(self.output_formats.currentText()).lower()

    @property
    def manually_fine_tune_toc(self):
        for w in self.widgets:
            if hasattr(w, 'manually_fine_tune_toc'):
                return w.manually_fine_tune_toc.isChecked()

    def setup_pipeline(self, *args):
        oidx = self.groups.currentIndex().row()
        input_format = self.input_format
        output_format = self.output_format
        self.plumber = create_dummy_plumber(input_format, output_format)

        def widget_factory(cls):
            return cls(self, self.plumber.get_option_by_name,
                       self.plumber.get_option_help, self.db, self.book_id)

        self.mw = widget_factory(MetadataWidget)
        self.setWindowTitle(_('Convert') + ' ' + str(self.mw.title.text()))
        lf = widget_factory(LookAndFeelWidget)
        hw = widget_factory(HeuristicsWidget)
        sr = widget_factory(SearchAndReplaceWidget)
        ps = widget_factory(PageSetupWidget)
        sd = widget_factory(StructureDetectionWidget)
        toc = widget_factory(TOCWidget)
        from calibre.gui2.actions.toc_edit import SUPPORTED
        toc.manually_fine_tune_toc.setVisible(
            output_format.upper() in SUPPORTED)
        debug = widget_factory(DebugWidget)

        output_widget = self.plumber.output_plugin.gui_configuration_widget(
            self, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)
        input_widget = self.plumber.input_plugin.gui_configuration_widget(
            self, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)

        self.break_cycles()
        self.widgets = widgets = [self.mw, lf, hw, ps, sd, toc, sr]
        if input_widget is not None:
            widgets.append(input_widget)
        if output_widget is not None:
            widgets.append(output_widget)
        widgets.append(debug)
        for w in widgets:
            w.set_help_signal.connect(self.help.setPlainText)
            w.setVisible(False)

        self._groups_model = GroupModel(widgets)
        self.groups.setModel(self._groups_model)

        idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0
        self.groups.setCurrentIndex(self._groups_model.index(idx))
        self.show_pane(idx)
        self.groups.selectionModel().currentChanged.connect(
            self.current_group_changed)
        try:
            shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True)
        except Exception:
            pass

    def setup_input_output_formats(self, db, book_id, preferred_input_format,
                                   preferred_output_format):
        if preferred_output_format:
            preferred_output_format = preferred_output_format.upper()
        output_formats = get_output_formats(preferred_output_format)
        input_format, input_formats = get_input_format_for_book(
            db, book_id, preferred_input_format)
        preferred_output_format = preferred_output_format if \
            preferred_output_format in output_formats else \
            sort_formats_by_preference(output_formats,
                    [prefs['output_format']])[0]
        self.input_formats.addItems(str(x.upper()) for x in input_formats)
        self.output_formats.addItems(str(x.upper()) for x in output_formats)
        self.input_formats.setCurrentIndex(input_formats.index(input_format))
        self.output_formats.setCurrentIndex(
            output_formats.index(preferred_output_format))

    def show_pane(self, index):
        if hasattr(index, 'row'):
            index = index.row()
        ow = self.scrollArea.takeWidget()
        if ow:
            ow.setParent(self)
        for i, w in enumerate(self.widgets):
            if i == index:
                self.scrollArea.setWidget(w)
                w.show()
            else:
                w.setVisible(False)

    def accept(self):
        recs = GuiRecommendations()
        for w in self._groups_model.widgets:
            if not w.pre_commit_check():
                return
            x = w.commit(save_defaults=False)
            recs.update(x)
        self.opf_file, self.cover_file = self.mw.opf_file, self.mw.cover_file
        self._recommendations = recs
        if self.db is not None:
            recs['gui_preferred_input_format'] = self.input_format
            save_specifics(self.db, self.book_id, recs)
        self.break_cycles()
        QDialog.accept(self)

    def reject(self):
        self.break_cycles()
        QDialog.reject(self)

    def done(self, r):
        if self.isVisible():
            gprefs['convert_single_dialog_geom'] = \
                bytearray(self.saveGeometry())
        return QDialog.done(self, r)

    def break_cycles(self):
        for w in self.widgets:
            w.break_cycles()

    @property
    def recommendations(self):
        recs = [(k, v, OptionRecommendation.HIGH)
                for k, v in self._recommendations.items()]
        return recs

    def show_group_help(self, index):
        widget = self._groups_model.widgets[index.row()]
        self.help.setPlainText(widget.HELP)
    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit Virtual library'))
        else:
            self.setWindowTitle(_('Create Virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = sl = QTextBrowser(self)
        sl.viewport().setAutoFillBackground(False)
        gl.addWidget(sl, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        # Trigger the textChanged signal to initialize the saved searches box
        self.vl_text.setText(' ')
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel('<p>'+_('Create a Virtual library based on: ')+
            ('<a href="author.{0}">{0}</a>, '
            '<a href="tag.{1}">{1}</a>, '
            '<a href="publisher.{2}">{2}</a>, '
            '<a href="series.{3}">{3}</a>, '
            '<a href="search.{4}">{4}</a>.').format(_('Authors'), _('Tags'),
                                            _('Publishers'), ngettext('Series', 'Series', 2), _('Saved searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3,10)

        self.hl = hl = QLabel(_('''
            <h2>Virtual libraries</h2>

            <p>With <i>Virtual libraries</i>, you can restrict calibre to only show
            you books that match a search. When a Virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the Virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual library to only show you books with the tag <i>Unread</i>
            or only books by <i>My favorite author</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="%s">User Manual</a>.</p>
            ''') % localize_user_manual_link('https://manual.calibre-ebook.com/virtual_libraries.html'))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(QFrame.Shape.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.new_api.pref('virtual_libraries', {})
            for dex,vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint()+QSize(150, 25))
Esempio n. 19
0
    def __init__(self, gui):
        QDialog.__init__(self, parent=gui)
        self.gui = gui
        self.setWindowTitle(_('Mark books with text label'))
        layout = QGridLayout()
        layout.setColumnStretch(1, 10)
        self.setLayout(layout)

        self.text_box = textbox = MyHistoryComboBox()
        textbox.initialize('mark_with_text')

        history = textbox.all_items
        button_rows = min(4, len(history) - 1)
        for i in range(0, button_rows):
            if i == 0:
                layout.addWidget(QLabel(_('Recently used values:')), 0, 0, 1,
                                 2)
            button = QPushButton()
            text = history[i + 1]
            button.setText(text)
            button.clicked.connect(partial(self.button_pushed, text=text))
            row = i + 1
            layout.addWidget(button, row, 1)
            label = QLabel('&' + str(row + 1))
            label.setBuddy(button)
            layout.addWidget(label, row, 0)
        if button_rows > 0:
            layout.addWidget(QLabel(_('Enter a value:')), button_rows + 1, 0,
                             1, 2)
        textbox.show_initial_value(history[0] if history else '')
        layout.addWidget(textbox, button_rows + 2, 1)
        textbox.setFocus()
        button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok
                                      | QDialogButtonBox.StandardButton.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box, button_rows + 3, 0, 1, 2)