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_type(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.StandardButton.Ok
            | QDialogButtonBox.StandardButton.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()
Exemple #2
0
    def __init__(self, current_cover=None, parent=None):
        QDialog.__init__(self, parent)
        self.current_cover = current_cover
        self.log = Log()
        self.book = self.cover_pixmap = None

        self.setWindowTitle(_('Downloading metadata...'))
        self.setWindowIcon(QIcon(I('download-metadata.png')))

        self.stack = QStackedWidget()
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.addWidget(self.stack)

        self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.bb.rejected.connect(self.reject)
        self.bb.accepted.connect(self.accept)
        self.ok_button = self.bb.button(QDialogButtonBox.StandardButton.Ok)
        self.ok_button.setEnabled(False)
        self.ok_button.clicked.connect(self.ok_clicked)
        self.prev_button = pb = QPushButton(QIcon(I('back.png')), _('&Back'), self)
        pb.clicked.connect(self.back_clicked)
        pb.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        self.log_button = self.bb.addButton(_('&View log'), QDialogButtonBox.ButtonRole.ActionRole)
        self.log_button.clicked.connect(self.view_log)
        self.log_button.setIcon(QIcon(I('debug.png')))
        self.prev_button.setVisible(False)
        h.addWidget(self.prev_button), h.addWidget(self.bb)

        self.identify_widget = IdentifyWidget(self.log, self)
        self.identify_widget.rejected.connect(self.reject)
        self.identify_widget.results_found.connect(self.identify_results_found)
        self.identify_widget.book_selected.connect(self.book_selected)
        self.stack.addWidget(self.identify_widget)

        self.covers_widget = CoversWidget(self.log, self.current_cover, parent=self)
        self.covers_widget.chosen.connect(self.ok_clicked)
        self.stack.addWidget(self.covers_widget)

        self.resize(850, 600)
        geom = gprefs.get('metadata_single_gui_geom', None)
        if geom is not None and geom:
            QApplication.instance().safe_restore_geometry(self, geom)

        self.finished.connect(self.cleanup)
 def __init__(self, val):
     QWidget.__init__(self)
     self.t = t = QLineEdit(self)
     t.setText(val or '')
     t.setCursorPosition(0)
     self.setMinimumWidth(400)
     self.l = l = QGridLayout(self)
     self.setLayout(l)
     self.m = m = QLabel('<p>'+_('''<b>Save &template</b> to control the filename and
     location of files sent to the device:'''))
     m.setWordWrap(True)
     m.setBuddy(t)
     l.addWidget(m, 0, 0, 1, 2)
     l.addWidget(t, 1, 0, 1, 1)
     b = self.b = QPushButton(_('&Template editor'))
     l.addWidget(b, 1, 1, 1, 1)
     b.clicked.connect(self.edit_template)
 def __init__(self, val, label=None, tooltip=None):
     super().__init__()
     self.l = l = QGridLayout(self)
     self.setLayout(l)
     col = 0
     if label is not None:
         l.addWidget(QLabel(label), 0, col, 1, 1)
         col += 1
     self.t = t = TemplateLineEditor(self)
     t.setText(val or '')
     t.setCursorPosition(0)
     self.setMinimumWidth(300)
     l.addWidget(t, 0, col, 1, 1)
     col += 1
     b = self.b = QPushButton(_('&Template editor'))
     l.addWidget(b, 0, col, 1, 1)
     b.clicked.connect(self.edit_template)
     self.setToolTip(wrap_msg(tooltip))
Exemple #5
0
    def setup_ui(self):
        self.resize(678, 430)
        self.setWindowTitle(_("Add books by ISBN"))
        self.setWindowIcon(QIcon(I('add_book.png')))
        self.l = l = QVBoxLayout(self)
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.bb = bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel, self)
        bb.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK'))
        l.addWidget(bb), bb.accepted.connect(self.accept), bb.rejected.connect(
            self.reject)
        self.ll = l = QVBoxLayout()
        h.addLayout(l)
        self.isbn_box = i = QPlainTextEdit(self)
        i.setFocus(Qt.FocusReason.OtherFocusReason)
        l.addWidget(i)
        self.paste_button = b = QPushButton(_("&Paste from clipboard"), self)
        l.addWidget(b), b.clicked.connect(self.paste)
        self.lll = l = QVBoxLayout()
        h.addLayout(l)
        self.label = la = QLabel(
            _("<p>Enter a list of ISBNs in the box to the left, one per line. calibre will automatically"
              " create entries for books based on the ISBN and download metadata and covers for them.</p>\n"
              "<p>Any invalid ISBNs in the list will be ignored.</p>\n"
              "<p>You can also specify a file that will be added with each ISBN. To do this enter the full"
              " path to the file after a <code>&gt;&gt;</code>. For example:</p>\n"
              "<p><code>9788842915232 &gt;&gt; %s</code></p>"), self)
        l.addWidget(la), la.setWordWrap(True)
        l.addSpacing(20)
        self.la2 = la = QLabel(_("&Tags to set on created book entries:"),
                               self)
        l.addWidget(la)
        self.add_tags = le = QLineEdit(self)
        le.setText(', '.join(gprefs.get('add from ISBN tags', [])))
        la.setBuddy(le)
        l.addWidget(le)
        self._check_for_existing = ce = QCheckBox(
            _('Check for books with the same ISBN already in library'), self)
        ce.setChecked(gprefs.get('add from ISBN dup check', False))
        l.addWidget(ce)

        l.addStretch(10)
Exemple #6
0
 def setup_ui(self):
     self.l = l = QVBoxLayout(self)
     self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Close)
     self.la = la = QLabel(self.LABEL)
     l.addWidget(la)
     self.tags = t = QLineEdit(self)
     la.setBuddy(t)
     t.setPlaceholderText(self.PLACEHOLDER)
     self.h = h = QHBoxLayout()
     l.addLayout(h)
     h.addWidget(t)
     self.test_button = b = QPushButton(_('&Test'), self)
     b.clicked.connect(self.do_test)
     h.addWidget(b)
     self.result = la = QLabel(self)
     la.setWordWrap(True)
     la.setText(self.EMPTY_RESULT)
     l.addWidget(la)
     l.addWidget(self.bb)
Exemple #7
0
 def __init__(self, parent=None):
     QMenu.__init__(self, parent)
     self.l = l = QHBoxLayout(self)
     l.setSpacing(20)
     self.items = []
     if parent is None:
         buttons = [
             QPushButton(QIcon(I(i + '.png')), i, self)
             for i in 'search tags cover_flow grid book'.split()
         ]
         for b in buttons:
             b.setVisible(False), b.setCheckable(True), b.setChecked(
                 b.text() in 'tags grid')
             b.label = b.text().capitalize()
     else:
         buttons = parent.layout_buttons
     for b in buttons:
         self.items.append(LayoutItem(b, self))
         l.addWidget(self.items[-1])
     self.current_item = None
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.h = h = QHBoxLayout()

        self.la = la = QLabel(self.MSG)
        la.setWordWrap(True)
        l.addWidget(la)
        l.addLayout(h)
        english_sentence = '{preamble} {match_type}'
        sentence = _('{preamble} {match_type}')
        if set(sentence.split()) != set(english_sentence.split()):
            sentence = english_sentence
        parts = sentence.split()
        for clause in parts:
            if clause == '{preamble}':
                self.preamble = w = QLabel(_('If the tag'))
            elif clause == '{match_type}':
                self.match_type = w = QComboBox(self)
                for action, m in MATCH_TYPE_MAP.items():
                    w.addItem(m.text, action)
                w.currentIndexChanged.connect(self.update_state)
            h.addWidget(w)
            if clause is not parts[-1]:
                h.addWidget(QLabel('\xa0'))
        h.addStretch(1)
        self.generic_query = gq = GenericEdit(self)
        self.css_query = cq = CSSEdit(self)
        self.xpath_query = xq = XPathEdit(
            self, object_name='html_transform_rules_xpath', show_msg=False)
        l.addWidget(gq), l.addWidget(cq), l.addWidget(xq)

        self.thenl = QLabel(_('Then:'))
        l.addWidget(self.thenl)
        self.actions = a = ActionsContainer(self)
        l.addWidget(a)
        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                                          _('Add another action'))
        b.clicked.connect(self.actions.new_action)
        l.addWidget(b)
        self.update_state()
    def __init__(self, device, rule=None):
        QWidget.__init__(self)
        self._device = weakref.ref(device)

        self.l = l = QHBoxLayout()
        self.setLayout(l)

        p, s = _('Send the %s format to the folder:').partition('%s')[0::2]
        self.l1 = l1 = QLabel(p)
        l.addWidget(l1)
        self.fmt = f = QComboBox(self)
        l.addWidget(f)
        self.l2 = l2 = QLabel(s)
        l.addWidget(l2)
        self.folder = f = QLineEdit(self)
        f.setPlaceholderText(_('Folder on the device'))
        l.addWidget(f)
        self.b = b = QToolButton()
        l.addWidget(b)
        b.setIcon(QIcon(I('document_open.png')))
        b.clicked.connect(self.browse)
        b.setToolTip(_('Browse for a folder on the device'))
        self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
                _('&Remove rule'), self)
        l.addWidget(rb)
        rb.clicked.connect(self.removed)

        for fmt in sorted(BOOK_EXTENSIONS):
            self.fmt.addItem(fmt.upper(), fmt.lower())

        self.fmt.setCurrentIndex(0)

        if rule is not None:
            fmt, folder = rule
            idx = self.fmt.findText(fmt.upper())
            if idx > -1:
                self.fmt.setCurrentIndex(idx)
            self.folder.setText(folder)

        self.ignore = False
Exemple #10
0
 def setup_ui(self):
     from calibre.gui2.tweak_book.editor.text import TextEdit
     self.l = l = QVBoxLayout(self)
     self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Close)
     self.la = la = QLabel(self.LABEL)
     l.addWidget(la)
     self.css = t = TextEdit(self)
     t.load_text('', self.SYNTAX)
     la.setBuddy(t)
     c = t.textCursor()
     c.movePosition(QTextCursor.MoveOperation.End)
     t.setTextCursor(c)
     self.h = h = QHBoxLayout()
     l.addLayout(h)
     h.addWidget(t)
     self.test_button = b = QPushButton(_('&Test'), self)
     b.clicked.connect(self.do_test)
     h.addWidget(b)
     self.result = la = TextEdit(self)
     la.setReadOnly(True)
     l.addWidget(la)
     l.addWidget(self.bb)
Exemple #11
0
    def genesis(self, gui):
        self.gui = gui
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        self.confirms_reset = False

        self.la = la = QLabel(
            _('The list of devices that you have asked calibre to ignore. '
              'Uncheck a device to have calibre stop ignoring it.'))
        la.setWordWrap(True)
        l.addWidget(la)

        self.devices = f = QListWidget(self)
        l.addWidget(f)
        f.itemChanged.connect(self.changed_signal)
        f.itemDoubleClicked.connect(self.toggle_item)

        self.la2 = la = QLabel(
            _('The list of device plugins you have disabled. Uncheck an entry '
              'to enable the plugin. calibre cannot detect devices that are '
              'managed by disabled plugins.'))
        la.setWordWrap(True)
        l.addWidget(la)

        self.device_plugins = f = QListWidget(f)
        l.addWidget(f)
        f.itemChanged.connect(self.changed_signal)
        f.itemDoubleClicked.connect(self.toggle_item)

        self.reset_confirmations_button = b = QPushButton(
            _('Reset allowed devices'))
        b.setToolTip(
            textwrap.fill(
                _('This will erase the list of devices that calibre knows about'
                  ' causing it to ask you for permission to manage them again,'
                  ' the next time they connect')))
        b.clicked.connect(self.reset_confirmations)
        l.addWidget(b)
Exemple #12
0
class FontFamilyChooser(QWidget):

    family_changed = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        self.button = QPushButton(self)
        self.button.setIcon(QIcon(I('font.png')))
        self.button.setSizePolicy(QSizePolicy.Policy.Fixed,
                                  QSizePolicy.Policy.Fixed)
        l.addWidget(self.button)
        self.default_text = _('Choose &font family')
        self.font_family = None
        self.button.clicked.connect(self.show_chooser)
        self.clear_button = QToolButton(self)
        self.clear_button.setIcon(QIcon(I('clear_left.png')))
        self.clear_button.clicked.connect(self.clear_family)
        l.addWidget(self.clear_button)
        self.setToolTip = self.button.setToolTip
        self.toolTip = self.button.toolTip
        self.clear_button.setToolTip(_('Clear the font family'))
        l.addStretch(1)

    def clear_family(self):
        self.font_family = None

    @property
    def font_family(self):
        return self._current_family

    @font_family.setter
    def font_family(self, val):
        if not val:
            val = None
        self._current_family = val
        self.button.setText(val or self.default_text)
        self.family_changed.emit(val)

    def show_chooser(self):
        d = FontFamilyDialog(self.font_family, self)
        if d.exec_() == QDialog.DialogCode.Accepted:
            self.font_family = d.font_family
Exemple #13
0
class UnpackBook(QDialog):

    def __init__(self, parent, book_id, fmts, db):
        QDialog.__init__(self, parent)
        self.setWindowIcon(QIcon(I('unpack-book.png')))
        self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db)
        self._exploded = None
        self._cleanup_dirs = []
        self._cleanup_files = []

        self.setup_ui()
        self.setWindowTitle(_('Unpack book') + ' - ' + db.title(book_id,
            index_is_id=True))

        button = self.fmt_choice_buttons[0]
        button_map = {str(x.text()):x for x in self.fmt_choice_buttons}
        of = prefs['output_format'].upper()
        df = tweaks.get('default_tweak_format', None)
        lf = gprefs.get('last_tweak_format', None)
        if df and df.lower() == 'remember' and lf in button_map:
            button = button_map[lf]
        elif df and df.upper() in button_map:
            button = button_map[df.upper()]
        elif of in button_map:
            button = button_map[of]
        button.setChecked(True)

        self.init_state()
        for button in self.fmt_choice_buttons:
            button.toggled.connect(self.init_state)

    def init_state(self, *args):
        self._exploded = None
        self.preview_button.setEnabled(False)
        self.rebuild_button.setEnabled(False)
        self.explode_button.setEnabled(True)

    def setup_ui(self):  # {{{
        self._g = g = QHBoxLayout(self)
        self.setLayout(g)
        self._l = l = QVBoxLayout()
        g.addLayout(l)

        fmts = sorted(x.upper() for x in self.fmts)
        self.fmt_choice_box = QGroupBox(_('Choose the format to unpack:'), self)
        self._fl = fl = QHBoxLayout()
        self.fmt_choice_box.setLayout(self._fl)
        self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts]
        for x in self.fmt_choice_buttons:
            fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
                    0)
        l.addWidget(self.fmt_choice_box)
        self.fmt_choice_box.setVisible(len(fmts) > 1)

        self.help_label = QLabel(_('''\
            <h2>About Unpack book</h2>
            <p>Unpack book allows you to fine tune the appearance of an e-book by
            making small changes to its internals. In order to use Unpack book,
            you need to know a little bit about HTML and CSS, technologies that
            are used in e-books. Follow the steps:</p>
            <br>
            <ol>
            <li>Click "Explode book": This will "explode" the book into its
            individual internal components.<br></li>
            <li>Right click on any individual file and select "Open with..." to
            edit it in your favorite text editor.<br></li>
            <li>When you are done: <b>close the file browser window
            and the editor windows you used to make your tweaks</b>. Then click
            the "Rebuild book" button, to update the book in your calibre
            library.</li>
            </ol>'''))
        self.help_label.setWordWrap(True)
        self._fr = QFrame()
        self._fr.setFrameShape(QFrame.Shape.VLine)
        g.addWidget(self._fr)
        g.addWidget(self.help_label)

        self._b = b = QGridLayout()
        left, top, right, bottom = b.getContentsMargins()
        top += top
        b.setContentsMargins(left, top, right, bottom)
        l.addLayout(b, stretch=10)

        self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode book'))
        self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview book'))
        self.cancel_button  = QPushButton(QIcon(I('window-close.png')), _('&Cancel'))
        self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild book'))

        self.explode_button.setToolTip(
                _('Explode the book to edit its components'))
        self.preview_button.setToolTip(
                _('Preview the result of your changes'))
        self.cancel_button.setToolTip(
                _('Abort without saving any changes'))
        self.rebuild_button.setToolTip(
            _('Save your changes and update the book in the calibre library'))

        a = b.addWidget
        a(self.explode_button, 0, 0, 1, 1)
        a(self.preview_button, 0, 1, 1, 1)
        a(self.cancel_button,  1, 0, 1, 1)
        a(self.rebuild_button, 1, 1, 1, 1)

        for x in ('explode', 'preview', 'cancel', 'rebuild'):
            getattr(self, x+'_button').clicked.connect(getattr(self, x))

        self.msg = QLabel('dummy', self)
        self.msg.setVisible(False)
        self.msg.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
            font-size: x-large;
            font-weight: bold;
        }
        ''')

        self.resize(self.sizeHint() + QSize(40, 10))
    # }}}

    def show_msg(self, msg):
        self.msg.setText(msg)
        self.msg.resize(self.size() - QSize(50, 25))
        self.msg.move((self.width() - self.msg.width())//2,
                (self.height() - self.msg.height())//2)
        self.msg.setVisible(True)

    def hide_msg(self):
        self.msg.setVisible(False)

    def explode(self):
        self.show_msg(_('Exploding, please wait...'))
        if len(self.fmt_choice_buttons) > 1:
            gprefs.set('last_tweak_format', self.current_format.upper())
        QTimer.singleShot(5, self.do_explode)

    def ask_question(self, msg):
        return question_dialog(self, _('Are you sure?'), msg)

    def do_explode(self):
        from calibre.ebooks.tweak import get_tools, Error, WorkerError
        tdir = PersistentTemporaryDirectory('_tweak_explode')
        self._cleanup_dirs.append(tdir)
        det_msg = None
        try:
            src = self.db.format(self.book_id, self.current_format,
                    index_is_id=True, as_path=True)
            self._cleanup_files.append(src)
            exploder = get_tools(self.current_format)[0]
            opf = exploder(src, tdir, question=self.ask_question)
        except WorkerError as e:
            det_msg = e.orig_tb
        except Error as e:
            return error_dialog(self, _('Failed to unpack'),
                (_('Could not explode the %s file.')%self.current_format) + ' ' + as_unicode(e), show=True)
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            return error_dialog(self, _('Failed to unpack'),
                _('Could not explode the %s file. Click "Show details" for '
                    'more information.')%self.current_format, det_msg=det_msg,
                show=True)

        if opf is None:
            # The question was answered with No
            return

        self._exploded = tdir
        self.explode_button.setEnabled(False)
        self.preview_button.setEnabled(True)
        self.rebuild_button.setEnabled(True)
        open_local_file(tdir)

    def rebuild_it(self):
        from calibre.ebooks.tweak import get_tools, WorkerError
        src_dir = self._exploded
        det_msg = None
        of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower())
        of.close()
        of = of.name
        self._cleanup_files.append(of)
        try:
            rebuilder = get_tools(self.current_format)[1]
            rebuilder(src_dir, of)
        except WorkerError as e:
            det_msg = e.orig_tb
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            error_dialog(self, _('Failed to rebuild file'),
                    _('Failed to rebuild %s. For more information, click '
                        '"Show details".')%self.current_format,
                    det_msg=det_msg, show=True)
            return None

        return of

    def preview(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_preview)

    def do_preview(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            self.parent().iactions['View']._view_file(rebuilt)

    def rebuild(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_rebuild)

    def do_rebuild(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            fmt = os.path.splitext(rebuilt)[1][1:].upper()
            with open(rebuilt, 'rb') as f:
                self.db.add_format(self.book_id, fmt, f, index_is_id=True)
            self.accept()

    def cancel(self):
        self.reject()

    def cleanup(self):
        if ismacos and self._exploded:
            try:
                import appscript
                self.finder = appscript.app('Finder')
                self.finder.Finder_windows[os.path.basename(self._exploded)].close()
            except:
                pass

        for f in self._cleanup_files:
            try:
                os.remove(f)
            except:
                pass

        for d in self._cleanup_dirs:
            try:
                shutil.rmtree(d)
            except:
                pass

    @property
    def db(self):
        return self.db_ref()

    @property
    def current_format(self):
        for b in self.fmt_choice_buttons:
            if b.isChecked():
                return str(b.text())
    def setup_ui(self):
        self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)

        self.bb.clear()
        self.bb.addButton(QDialogButtonBox.StandardButton.Close)
        self.splitter = s = QSplitter(self)
        self.h = h = QHBoxLayout()
        h.setContentsMargins(0, 0, 0, 0)
        self.install_fonts_button = b = QPushButton(_('&Install fonts'), self)
        h.addWidget(b), b.setIcon(QIcon(I('plus.png')))
        b.setToolTip(textwrap.fill(_('Install fonts from .ttf/.otf files to make them available for embedding')))
        b.clicked.connect(self.install_fonts)
        l.addWidget(s), l.addLayout(h), h.addStretch(10), h.addWidget(self.bb)

        self.fonts_view = fv = QTableView(self)
        fv.doubleClicked.connect(self.show_embedding_data)
        self.model = m = AllFonts(fv)
        fv.horizontalHeader().setStretchLastSection(True)
        fv.setModel(m)
        fv.setSortingEnabled(True)
        fv.setShowGrid(False)
        fv.setAlternatingRowColors(True)
        fv.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
        fv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        fv.horizontalHeader().setSortIndicator(1, Qt.SortOrder.AscendingOrder)
        self.container = c = QWidget()
        l = c.l = QVBoxLayout(c)
        c.setLayout(l)
        s.addWidget(fv), s.addWidget(c)

        self.cb = b = QPushButton(_('&Change selected fonts'))
        b.setIcon(QIcon(I('wizard.png')))
        b.clicked.connect(self.change_fonts)
        l.addWidget(b)
        self.rb = b = QPushButton(_('&Remove selected fonts'))
        b.clicked.connect(self.remove_fonts)
        b.setIcon(QIcon(I('trash.png')))
        l.addWidget(b)
        self.eb = b = QPushButton(_('&Embed all fonts'))
        b.setIcon(QIcon(I('embed-fonts.png')))
        b.clicked.connect(self.embed_fonts)
        l.addWidget(b)
        self.sb = b = QPushButton(_('&Subset all fonts'))
        b.setIcon(QIcon(I('subset-fonts.png')))
        b.clicked.connect(self.subset_fonts)
        l.addWidget(b)
        self.refresh_button = b = self.bb.addButton(_('&Refresh'), QDialogButtonBox.ButtonRole.ActionRole)
        b.setToolTip(_('Rescan the book for fonts in case you have made changes'))
        b.setIcon(QIcon(I('view-refresh.png')))
        b.clicked.connect(self.refresh)

        self.la = la = QLabel(
            '<p>' + _(
            ''' All the fonts declared in this book are shown to the left, along with whether they are embedded or not.
            You can remove or replace any selected font and also embed any declared fonts that are not already embedded.''') + '<p>' + _(
            ''' Double click any font family to see if the font is available for embedding on your computer. ''')
        )
        la.setWordWrap(True)
        l.addWidget(la)

        l.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
Exemple #15
0
class ChooseFormatDialog(QDialog):
    def __init__(self, window, msg, formats, show_open_with=False):
        QDialog.__init__(self, window)
        self.resize(507, 377)
        self.setWindowIcon(QIcon(I("mimetypes/unknown.png")))
        self.setWindowTitle(_('Choose format'))
        self.l = l = QVBoxLayout(self)
        self.msg = QLabel(msg)
        l.addWidget(self.msg)
        self.formats = QListWidget(self)
        self.formats.setIconSize(QSize(64, 64))
        self.formats.activated[QModelIndex].connect(self.activated_slot)
        l.addWidget(self.formats)
        self.h = h = QHBoxLayout()
        h.setContentsMargins(0, 0, 0, 0)
        l.addLayout(h)
        if show_open_with:
            self.owb = QPushButton(_('&Open with...'), self)
            self.formats.currentRowChanged.connect(
                self.update_open_with_button)
            h.addWidget(self.owb)
            self.own = QMenu(self.owb.text())
            self.owb.setMenu(self.own)
            self.own.aboutToShow.connect(self.populate_open_with)
        self.buttonBox = bb = QDialogButtonBox(self)
        bb.setStandardButtons(QDialogButtonBox.StandardButton.Ok
                              | QDialogButtonBox.StandardButton.Cancel)
        bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
        h.addStretch(10), h.addWidget(self.buttonBox)

        formats = list(formats)
        for format in formats:
            self.formats.addItem(
                QListWidgetItem(
                    file_icon_provider().icon_from_ext(format.lower()),
                    format.upper()))
        self._formats = formats
        self.formats.setCurrentRow(0)
        self._format = self.open_with_format = None
        if show_open_with:
            self.populate_open_with()
            self.update_open_with_button()

    def populate_open_with(self):
        from calibre.gui2.open_with import populate_menu, edit_programs
        menu = self.own
        menu.clear()
        fmt = self._formats[self.formats.currentRow()]

        def connect_action(ac, entry):
            connect_lambda(ac.triggered, self,
                           lambda self: self.open_with(entry))

        populate_menu(menu, connect_action, fmt)
        if len(menu.actions()) == 0:
            menu.addAction(
                _('Open %s with...') % fmt.upper(), self.choose_open_with)
        else:
            menu.addSeparator()
            menu.addAction(
                _('Add other application for %s files...') % fmt.upper(),
                self.choose_open_with)
            menu.addAction(_('Edit "Open with" applications...'),
                           partial(edit_programs, fmt, self))

    def update_open_with_button(self):
        fmt = self._formats[self.formats.currentRow()]
        self.owb.setText(_('Open %s with...') % fmt)

    def open_with(self, entry):
        self.open_with_format = (self._formats[self.formats.currentRow()],
                                 entry)
        self.accept()

    def choose_open_with(self):
        from calibre.gui2.open_with import choose_program
        fmt = self._formats[self.formats.currentRow()]
        entry = choose_program(fmt, self)
        if entry is not None:
            self.open_with(entry)

    def book_converted(self, book_id, fmt):
        fmt = fmt.upper()
        if fmt not in self._formats:
            self._formats.append(fmt)
            self.formats.addItem(
                QListWidgetItem(
                    file_icon_provider().icon_from_ext(fmt.lower()),
                    fmt.upper()))

    def activated_slot(self, *args):
        self.accept()

    def format(self):
        return self._format

    def accept(self):
        self._format = self._formats[self.formats.currentRow()]
        return QDialog.accept(self)
Exemple #16
0
 def __init__(self, initial_color=None, parent=None, choose_text=None):
     QPushButton.__init__(self, parent)
     self._color = None
     self.choose_text = choose_text or _('Choose &color')
     self.color = initial_color
     self.clicked.connect(self.choose_color)
Exemple #17
0
 def button(icon, text, tt, target):
     b = QPushButton(QIcon(I(icon)), text, self)
     b.setToolTip(tt)
     b.setFocusPolicy(Qt.FocusPolicy.NoFocus)
     b.clicked.connect(target)
     return b
Exemple #18
0
    def genesis(self, gui):
        self.gui = gui
        if not ismacos and not iswindows:
            self.label_widget_style.setVisible(False)
            self.opt_ui_style.setVisible(False)

        db = gui.library_view.model().db

        r = self.register

        try:
            self.icon_theme_title = json.loads(I('icon-theme.json',
                                                 data=True))['name']
        except Exception:
            self.icon_theme_title = _('Default icons')
        self.icon_theme.setText(
            _('Icon theme: <b>%s</b>') % self.icon_theme_title)
        self.commit_icon_theme = None
        self.icon_theme_button.clicked.connect(self.choose_icon_theme)
        self.default_author_link = DefaultAuthorLink(
            self.default_author_link_container)
        self.default_author_link.changed_signal.connect(self.changed_signal)
        r('gui_layout',
          config,
          restart_required=True,
          choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
        r('hidpi',
          gprefs,
          restart_required=True,
          choices=[(_('Automatic'), 'auto'), (_('On'), 'on'),
                   (_('Off'), 'off')])
        if ismacos:
            self.opt_hidpi.setVisible(False), self.label_hidpi.setVisible(
                False)
        r('ui_style',
          gprefs,
          restart_required=True,
          choices=[(_('System default'), 'system'),
                   (_('calibre style'), 'calibre')])
        r('book_list_tooltips', gprefs)
        r('dnd_merge', gprefs)
        r('wrap_toolbar_text', gprefs, restart_required=True)
        r('show_layout_buttons', gprefs, restart_required=True)
        r('row_numbers_in_book_list', gprefs)
        r('tag_browser_old_look', gprefs)
        r('tag_browser_hide_empty_categories', gprefs)
        r('tag_browser_always_autocollapse', gprefs)
        r('tag_browser_show_tooltips', gprefs)
        r('tag_browser_allow_keyboard_focus', gprefs)
        r('bd_show_cover', gprefs)
        r('bd_overlay_cover_size', gprefs)
        r('cover_grid_width', gprefs)
        r('cover_grid_height', gprefs)
        r('cover_grid_cache_size_multiple', gprefs)
        r('cover_grid_disk_cache_size', gprefs)
        r('cover_grid_spacing', gprefs)
        r('cover_grid_show_title', gprefs)
        r('tag_browser_show_counts', gprefs)
        r('tag_browser_item_padding', gprefs)
        r('books_autoscroll_time', gprefs)

        r('qv_respects_vls', gprefs)
        r('qv_dclick_changes_column', gprefs)
        r('qv_retkey_changes_column', gprefs)
        r('qv_follows_column', gprefs)

        r('cover_flow_queue_length', config, restart_required=True)
        r('cover_browser_reflections', gprefs)
        r('cover_browser_title_template', db.prefs)
        fm = db.field_metadata
        r('cover_browser_subtitle_field',
          db.prefs,
          choices=[(_('No subtitle'), 'none')] + sorted(
              (fm[k].get('name'), k)
              for k in fm.all_field_keys() if fm[k].get('name')))
        r('emblem_size', gprefs)
        r('emblem_position',
          gprefs,
          choices=[(_('Left'), 'left'), (_('Top'), 'top'),
                   (_('Right'), 'right'), (_('Bottom'), 'bottom')])
        r('book_list_extra_row_spacing', gprefs)
        r('booklist_grid', gprefs)
        r('book_details_comments_heading_pos',
          gprefs,
          choices=[(_('Never'), 'hide'), (_('Above text'), 'above'),
                   (_('Beside text'), 'side')])
        self.cover_browser_title_template_button.clicked.connect(
            self.edit_cb_title_template)
        self.id_links_button.clicked.connect(self.edit_id_link_rules)

        def get_esc_lang(l):
            if l == 'en':
                return 'English'
            return get_language(l)

        lang = get_lang()
        if lang is None or lang not in available_translations():
            lang = 'en'
        items = [(l, get_esc_lang(l)) for l in available_translations()
                 if l != lang]
        if lang != 'en':
            items.append(('en', get_esc_lang('en')))
        items.sort(key=lambda x: x[1].lower())
        choices = [(y, x) for x, y in items]
        # Default language is the autodetected one
        choices = [(get_language(lang), lang)] + choices
        r('language', prefs, choices=choices, restart_required=True)

        r('show_avg_rating', config)
        r('disable_animations', config)
        r('systray_icon', config, restart_required=True)
        r('show_splash_screen', gprefs)
        r('disable_tray_notification', config)
        r('use_roman_numerals_for_series_number', config)
        r('separate_cover_flow', config, restart_required=True)
        r('cb_fullscreen', gprefs)
        r('cb_preserve_aspect_ratio', gprefs)
        r('cb_double_click_to_activate', gprefs)

        choices = [(_('Off'), 'off'), (_('Small'), 'small'),
                   (_('Medium'), 'medium'), (_('Large'), 'large')]
        r('toolbar_icon_size', gprefs, choices=choices)

        choices = [(_('If there is enough room'), 'auto'),
                   (_('Always'), 'always'), (_('Never'), 'never')]
        r('toolbar_text', gprefs, choices=choices)

        choices = [(_('Disabled'), 'disable'),
                   (_('By first letter'), 'first letter'),
                   (_('Partitioned'), 'partition')]
        r('tags_browser_partition_method', gprefs, choices=choices)
        r('tags_browser_collapse_at', gprefs)
        r('tags_browser_collapse_fl_at', gprefs)

        choices = {
            k
            for k in db.field_metadata.all_field_keys()
            if (db.field_metadata[k]['is_category'] and
                (db.field_metadata[k]['datatype'] in
                 ['text', 'series', 'enumeration'])
                and not db.field_metadata[k]['display'].get('is_names', False))
            or (db.field_metadata[k]['datatype'] in ['composite'] and
                db.field_metadata[k]['display'].get('make_category', False))
        }
        choices |= {'search'}
        r('tag_browser_dont_collapse',
          gprefs,
          setting=CommaSeparatedList,
          choices=sorted(choices, key=sort_key))

        choices -= {'authors', 'publisher', 'formats', 'news', 'identifiers'}
        r('categories_using_hierarchy',
          db.prefs,
          setting=CommaSeparatedList,
          choices=sorted(choices, key=sort_key))

        fm = db.field_metadata
        choices = sorted(
            ((fm[k]['name'], k)
             for k in fm.displayable_field_keys() if fm[k]['name']),
            key=lambda x: sort_key(x[0]))
        r('field_under_covers_in_grid', db.prefs, choices=choices)

        self.current_font = self.initial_font = None
        self.change_font_button.clicked.connect(self.change_font)

        self.display_model = DisplayedFields(self.gui.current_db,
                                             self.field_display_order)
        self.display_model.dataChanged.connect(self.changed_signal)
        self.field_display_order.setModel(self.display_model)
        connect_lambda(
            self.df_up_button.clicked, self, lambda self: move_field_up(
                self.field_display_order, self.display_model))
        connect_lambda(
            self.df_down_button.clicked, self, lambda self: move_field_down(
                self.field_display_order, self.display_model))

        self.qv_display_model = QVDisplayedFields(self.gui.current_db,
                                                  self.qv_display_order)
        self.qv_display_model.dataChanged.connect(self.changed_signal)
        self.qv_display_order.setModel(self.qv_display_model)
        connect_lambda(
            self.qv_up_button.clicked, self, lambda self: move_field_up(
                self.qv_display_order, self.qv_display_model))
        connect_lambda(
            self.qv_down_button.clicked, self, lambda self: move_field_down(
                self.qv_display_order, self.qv_display_model))

        self.edit_rules = EditRules(self.tabWidget)
        self.edit_rules.changed.connect(self.changed_signal)
        self.tabWidget.addTab(self.edit_rules,
                              QIcon(I('format-fill-color.png')),
                              _('Column &coloring'))

        self.icon_rules = EditRules(self.tabWidget)
        self.icon_rules.changed.connect(self.changed_signal)
        self.tabWidget.addTab(self.icon_rules, QIcon(I('icon_choose.png')),
                              _('Column &icons'))

        self.grid_rules = EditRules(self.emblems_tab)
        self.grid_rules.changed.connect(self.changed_signal)
        self.emblems_tab.setLayout(QVBoxLayout())
        self.emblems_tab.layout().addWidget(self.grid_rules)

        self.tabWidget.setCurrentIndex(0)
        keys = [
            QKeySequence('F11', QKeySequence.SequenceFormat.PortableText),
            QKeySequence('Ctrl+Shift+F',
                         QKeySequence.SequenceFormat.PortableText)
        ]
        keys = [
            str(x.toString(QKeySequence.SequenceFormat.NativeText))
            for x in keys
        ]
        self.fs_help_msg.setText(
            self.fs_help_msg.text() %
            (QKeySequence(QKeySequence.StandardKey.FullScreen).toString(
                QKeySequence.SequenceFormat.NativeText)))
        self.size_calculated.connect(self.update_cg_cache_size,
                                     type=Qt.ConnectionType.QueuedConnection)
        self.tabWidget.currentChanged.connect(self.tab_changed)

        l = self.cg_background_box.layout()
        self.cg_bg_widget = w = Background(self)
        l.addWidget(w, 0, 0, 3, 1)
        self.cover_grid_color_button = b = QPushButton(_('Change &color'),
                                                       self)
        b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        l.addWidget(b, 0, 1)
        b.clicked.connect(self.change_cover_grid_color)
        self.cover_grid_texture_button = b = QPushButton(
            _('Change &background image'), self)
        b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        l.addWidget(b, 1, 1)
        b.clicked.connect(self.change_cover_grid_texture)
        self.cover_grid_default_appearance_button = b = QPushButton(
            _('Restore default &appearance'), self)
        b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
        l.addWidget(b, 2, 1)
        b.clicked.connect(self.restore_cover_grid_appearance)
        self.cover_grid_empty_cache.clicked.connect(self.empty_cache)
        self.cover_grid_open_cache.clicked.connect(self.open_cg_cache)
        connect_lambda(self.cover_grid_smaller_cover.clicked, self,
                       lambda self: self.resize_cover(True))
        connect_lambda(self.cover_grid_larger_cover.clicked, self,
                       lambda self: self.resize_cover(False))
        self.cover_grid_reset_size.clicked.connect(self.cg_reset_size)
        self.opt_cover_grid_disk_cache_size.setMinimum(
            self.gui.grid_view.thumbnail_cache.min_disk_cache)
        self.opt_cover_grid_disk_cache_size.setMaximum(
            self.gui.grid_view.thumbnail_cache.min_disk_cache * 100)
        self.opt_cover_grid_width.valueChanged.connect(
            self.update_aspect_ratio)
        self.opt_cover_grid_height.valueChanged.connect(
            self.update_aspect_ratio)
        self.opt_book_details_css.textChanged.connect(self.changed_signal)
        from calibre.gui2.tweak_book.editor.text import get_highlighter, get_theme
        self.css_highlighter = get_highlighter('css')()
        self.css_highlighter.apply_theme(get_theme(None))
        self.css_highlighter.set_document(self.opt_book_details_css.document())
Exemple #19
0
    def __init__(self, gui, initial_plugin=None, close_after_initial=False):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.must_restart = False
        self.do_restart = False
        self.committed = False
        self.close_after_initial = close_after_initial

        self.resize(930, 720)
        nh, nw = min_available_height() - 25, available_width() - 10
        if nh < 0:
            nh = 800
        if nw < 0:
            nw = 600
        nh = min(self.height(), nh)
        nw = min(self.width(), nw)
        self.resize(nw, nh)

        geom = gprefs.get('preferences dialog geometry', None)
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)

        # Center
        if islinux:
            self.move(gui.rect().center() - self.rect().center())

        self.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.setWindowTitle(__appname__ + ' — ' + _('Preferences'))
        self.setWindowIcon(QIcon(I('config.png')))
        self.l = l = QVBoxLayout(self)

        self.stack = QStackedWidget(self)
        self.bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Close
            | QDialogButtonBox.StandardButton.Apply
            | QDialogButtonBox.StandardButton.Cancel
            | QDialogButtonBox.StandardButton.RestoreDefaults)
        self.bb.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(
            self.accept)
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).setIcon(
                QIcon(I('clear_left.png')))
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).clicked.connect(
                self.restore_defaults)
        self.wizard_button = QPushButton(QIcon(I('wizard.png')),
                                         _('Run Welcome &wizard'))
        self.wizard_button.clicked.connect(
            self.run_wizard, type=Qt.ConnectionType.QueuedConnection)
        self.wizard_button.setAutoDefault(False)
        self.bb.rejected.connect(self.reject)
        self.browser = Browser(self)
        self.browser.show_plugin.connect(self.show_plugin)
        self.stack.addWidget(self.browser)
        self.scroll_area = QScrollArea(self)
        self.stack.addWidget(self.scroll_area)
        self.scroll_area.setWidgetResizable(True)

        self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
        self.title_bar = TitleBar(self)
        for ac, tt in [(QDialogButtonBox.StandardButton.Apply,
                        _('Save changes')),
                       (QDialogButtonBox.StandardButton.Cancel,
                        _('Cancel and return to overview'))]:
            self.bb.button(ac).setToolTip(tt)

        l.addWidget(self.title_bar), l.addWidget(self.stack)
        h = QHBoxLayout()
        l.addLayout(h)
        h.addWidget(self.wizard_button), h.addStretch(10), h.addWidget(self.bb)

        if initial_plugin is not None:
            category, name = initial_plugin[:2]
            plugin = get_plugin(category, name)
            if plugin is not None:
                self.show_plugin(plugin)
                if len(initial_plugin) > 2:
                    w = self.findChild(QWidget, initial_plugin[2])
                    if w is not None:
                        for c in self.showing_widget.children():
                            if isinstance(c, QTabWidget):
                                idx = c.indexOf(w)
                                if idx > -1:
                                    c.setCurrentIndex(idx)
                                    break
        else:
            self.hide_plugin()
Exemple #20
0
    def __init__(self, parent, prefs=None):
        QWidget.__init__(self, parent)
        self.prefs = prefs or gprefs
        self.pending_search = None
        self.current_frag = None
        self.setLayout(QVBoxLayout())

        self.la = la = QLabel(
            '<b>' + _('Select a destination for the Table of Contents entry'))
        self.layout().addWidget(la)
        self.splitter = sp = QSplitter(self)
        self.layout().addWidget(sp)
        self.layout().setStretch(1, 10)
        sp.setOpaqueResize(False)
        sp.setChildrenCollapsible(False)

        self.dest_list = dl = QListWidget(self)
        dl.setMinimumWidth(250)
        dl.currentItemChanged.connect(self.current_changed)
        sp.addWidget(dl)

        w = self.w = QWidget(self)
        l = w.l = QGridLayout()
        w.setLayout(l)
        self.view = WebView(self, self.prefs)
        self.view.elem_clicked.connect(self.elem_clicked)
        self.view.frag_shown.connect(self.update_dest_label,
                                     type=Qt.ConnectionType.QueuedConnection)
        self.view.loadFinished.connect(self.load_finished,
                                       type=Qt.ConnectionType.QueuedConnection)
        l.addWidget(self.view, 0, 0, 1, 3)
        sp.addWidget(w)

        self.search_text = s = QLineEdit(self)
        s.setPlaceholderText(_('Search for text...'))
        s.returnPressed.connect(self.find_next)
        l.addWidget(s, 1, 0)
        self.ns_button = b = QPushButton(QIcon(I('arrow-down.png')),
                                         _('Find &next'), self)
        b.clicked.connect(self.find_next)
        l.addWidget(b, 1, 1)
        self.ps_button = b = QPushButton(QIcon(I('arrow-up.png')),
                                         _('Find &previous'), self)
        l.addWidget(b, 1, 2)
        b.clicked.connect(self.find_previous)

        self.f = f = QFrame()
        f.setFrameShape(QFrame.Shape.StyledPanel)
        f.setMinimumWidth(250)
        l = f.l = QVBoxLayout()
        f.setLayout(l)
        sp.addWidget(f)

        f.la = la = QLabel('<p>' + _(
            'Here you can choose a destination for the Table of Contents\' entry'
            ' to point to. First choose a file from the book in the left-most panel. The'
            ' file will open in the central panel.<p>'
            'Then choose a location inside the file. To do so, simply click on'
            ' the place in the central panel that you want to use as the'
            ' destination. As you move the mouse around the central panel, a'
            ' thick green line appears, indicating the precise location'
            ' that will be selected when you click.'))
        la.setStyleSheet('QLabel { margin-bottom: 20px }')
        la.setWordWrap(True)
        l.addWidget(la)

        f.la2 = la = QLabel('<b>' + _('Na&me of the ToC entry:'))
        l.addWidget(la)
        self.name = QLineEdit(self)
        self.name.setPlaceholderText(_('(Untitled)'))
        la.setBuddy(self.name)
        l.addWidget(self.name)

        self.base_msg = '<b>' + _('Currently selected destination:') + '</b>'
        self.dest_label = la = QLabel(self.base_msg)
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-top: 20px }')
        l.addWidget(la)

        l.addStretch()

        state = self.prefs.get('toc_edit_splitter_state', None)
        if state is not None:
            sp.restoreState(state)
Exemple #21
0
    def __init__(self, recipe_model, parent=None):
        QDialog.__init__(self, parent)
        self.commit_on_change = True
        self.previous_urn = None

        self.setWindowIcon(QIcon(I('scheduler.png')))
        self.l = l = QGridLayout(self)

        # Left panel
        self.h = h = QHBoxLayout()
        l.addLayout(h, 0, 0, 1, 1)
        self.search = s = SearchBox2(self)
        self.search.initialize('scheduler_search_history')
        self.search.setMinimumContentsLength(15)
        self.go_button = b = QToolButton(self)
        b.setText(_("Go"))
        b.clicked.connect(self.search.do_search)
        h.addWidget(s), h.addWidget(b)
        self.recipes = RecipesView(self)
        l.addWidget(self.recipes, 1, 0, 2, 1)
        self.recipe_model = recipe_model
        self.recipe_model.do_refresh()
        self.recipes.setModel(self.recipe_model)
        self.recipes.setFocus(Qt.FocusReason.OtherFocusReason)
        self.recipes.item_activated.connect(self.download_clicked)
        self.setWindowTitle(
            _("Schedule news download [{} sources]").format(
                self.recipe_model.showing_count))
        self.search.search.connect(self.recipe_model.search)
        self.recipe_model.searched.connect(
            self.search.search_done, type=Qt.ConnectionType.QueuedConnection)
        self.recipe_model.searched.connect(self.search_done)

        # Right Panel
        self.scroll_area_contents = sac = QWidget(self)
        self.l.addWidget(sac, 0, 1, 2, 1)
        sac.v = v = QVBoxLayout(sac)
        v.setContentsMargins(0, 0, 0, 0)
        self.detail_box = QTabWidget(self)
        self.detail_box.setVisible(False)
        self.detail_box.setCurrentIndex(0)
        v.addWidget(self.detail_box)
        v.addItem(
            QSpacerItem(20, 40, QSizePolicy.Policy.Minimum,
                        QSizePolicy.Policy.Expanding))

        # First Tab (scheduling)
        self.tab = QWidget()
        self.detail_box.addTab(self.tab, _("&Schedule"))
        self.tab.v = vt = QVBoxLayout(self.tab)
        vt.setContentsMargins(0, 0, 0, 0)
        self.blurb = la = QLabel('blurb')
        la.setWordWrap(True), la.setOpenExternalLinks(True)
        vt.addWidget(la)
        self.frame = f = QFrame(self.tab)
        vt.addWidget(f)
        f.setFrameShape(QFrame.Shape.StyledPanel)
        f.setFrameShadow(QFrame.Shadow.Raised)
        f.v = vf = QVBoxLayout(f)
        self.schedule = s = QCheckBox(_("&Schedule for download:"), f)
        self.schedule.stateChanged[int].connect(self.toggle_schedule_info)
        vf.addWidget(s)
        f.h = h = QHBoxLayout()
        vf.addLayout(h)
        self.days_of_week = QRadioButton(_("&Days of  week"), f)
        self.days_of_month = QRadioButton(_("Da&ys of month"), f)
        self.every_x_days = QRadioButton(_("Every &x days"), f)
        self.days_of_week.setChecked(True)
        h.addWidget(self.days_of_week), h.addWidget(
            self.days_of_month), h.addWidget(self.every_x_days)
        self.schedule_stack = ss = QStackedWidget(f)
        self.schedule_widgets = []
        for key in reversed(self.SCHEDULE_TYPES):
            self.schedule_widgets.insert(0, self.SCHEDULE_TYPES[key](self))
            self.schedule_stack.insertWidget(0, self.schedule_widgets[0])
        vf.addWidget(ss)
        self.last_downloaded = la = QLabel(f)
        la.setWordWrap(True)
        vf.addWidget(la)
        self.account = acc = QGroupBox(self.tab)
        acc.setTitle(_("&Account"))
        vt.addWidget(acc)
        acc.g = g = QGridLayout(acc)
        acc.unla = la = QLabel(_("&Username:"******"&Password:"******"&Show password"), self.account)
        spw.stateChanged[int].connect(self.set_pw_echo_mode)
        g.addWidget(spw, 2, 0, 1, 2)
        self.rla = la = QLabel(
            _("For the scheduling to work, you must leave calibre running."))
        vt.addWidget(la)
        for b, c in iteritems(self.SCHEDULE_TYPES):
            b = getattr(self, b)
            b.toggled.connect(self.schedule_type_selected)
            b.setToolTip(textwrap.dedent(c.HELP))

        # Second tab (advanced settings)
        self.tab2 = t2 = QWidget()
        self.detail_box.addTab(self.tab2, _("&Advanced"))
        self.tab2.g = g = QGridLayout(t2)
        g.setContentsMargins(0, 0, 0, 0)
        self.add_title_tag = tt = QCheckBox(_("Add &title as tag"), t2)
        g.addWidget(tt, 0, 0, 1, 2)
        t2.la = la = QLabel(_("&Extra tags:"))
        self.custom_tags = ct = QLineEdit(self)
        la.setBuddy(ct)
        g.addWidget(la), g.addWidget(ct, 1, 1)
        t2.la2 = la = QLabel(_("&Keep at most:"))
        la.setToolTip(
            _("Maximum number of copies (issues) of this recipe to keep.  Set to 0 to keep all (disable)."
              ))
        self.keep_issues = ki = QSpinBox(t2)
        tt.toggled['bool'].connect(self.keep_issues.setEnabled)
        ki.setMaximum(100000), la.setBuddy(ki)
        ki.setToolTip(
            _("<p>When set, this option will cause calibre to keep, at most, the specified number of issues"
              " of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the"
              " total is larger than this number.\n<p>Note that this feature only works if you have the"
              " option to add the title as tag checked, above.\n<p>Also, the setting for deleting periodicals"
              " older than a number of days, below, takes priority over this setting."
              ))
        ki.setSpecialValueText(_("all issues")), ki.setSuffix(_(" issues"))
        g.addWidget(la), g.addWidget(ki, 2, 1)
        si = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum,
                         QSizePolicy.Policy.Expanding)
        g.addItem(si, 3, 1, 1, 1)

        # Bottom area
        self.hb = h = QHBoxLayout()
        self.l.addLayout(h, 2, 1, 1, 1)
        self.labt = la = QLabel(_("Delete downloaded &news older than:"))
        self.old_news = on = QSpinBox(self)
        on.setToolTip(
            _("<p>Delete downloaded news older than the specified number of days. Set to zero to disable.\n"
              "<p>You can also control the maximum number of issues of a specific periodical that are kept"
              " by clicking the Advanced tab for that periodical above."))
        on.setSpecialValueText(_("never delete")), on.setSuffix(_(" days"))
        on.setMaximum(1000), la.setBuddy(on)
        on.setValue(gconf['oldest_news'])
        h.addWidget(la), h.addWidget(on)
        self.download_all_button = b = QPushButton(
            QIcon(I('news.png')), _("Download &all scheduled"), self)
        b.setToolTip(_("Download all scheduled news sources at once"))
        b.clicked.connect(self.download_all_clicked)
        self.l.addWidget(b, 3, 0, 1, 1)
        self.bb = bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel, self)
        bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
        self.download_button = b = bb.addButton(
            _('&Download now'), QDialogButtonBox.ButtonRole.ActionRole)
        b.setIcon(QIcon(I('arrow-down.png'))), b.setVisible(False)
        b.clicked.connect(self.download_clicked)
        self.l.addWidget(bb, 3, 1, 1, 1)

        geom = gprefs.get('scheduler_dialog_geometry')
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)
Exemple #22
0
    def setupUi(self, x):
        self.l = l = QVBoxLayout(self)
        self.la1 = la = QLabel(
            _("Values for the tweaks are shown below. Edit them to change the behavior of calibre."
              " Your changes will only take effect <b>after a restart</b> of calibre."
              ))
        l.addWidget(la), la.setWordWrap(True)
        self.splitter = s = QSplitter(self)
        s.setChildrenCollapsible(False)
        l.addWidget(s, 10)

        self.lv = lv = QWidget(self)
        lv.l = l2 = QVBoxLayout(lv)
        l2.setContentsMargins(0, 0, 0, 0)
        self.tweaks_view = tv = TweaksView(self)
        l2.addWidget(tv)
        self.plugin_tweaks_button = b = QPushButton(self)
        b.setToolTip(
            _("Edit tweaks for any custom plugins you have installed"))
        b.setText(_("&Plugin tweaks"))
        l2.addWidget(b)
        s.addWidget(lv)

        self.lv1 = lv = QWidget(self)
        s.addWidget(lv)
        lv.g = g = QGridLayout(lv)
        g.setContentsMargins(0, 0, 0, 0)

        self.search = sb = SearchBox2(self)
        sb.sizePolicy().setHorizontalStretch(10)
        sb.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLength)
        sb.setMinimumContentsLength(10)
        g.setColumnStretch(0, 100)
        g.addWidget(self.search, 0, 0, 1, 1)
        self.next_button = b = QPushButton(self)
        b.setIcon(QIcon(I("arrow-down.png")))
        b.setText(_("&Next"))
        g.addWidget(self.next_button, 0, 1, 1, 1)
        self.previous_button = b = QPushButton(self)
        b.setIcon(QIcon(I("arrow-up.png")))
        b.setText(_("&Previous"))
        g.addWidget(self.previous_button, 0, 2, 1, 1)

        self.hb = hb = QGroupBox(self)
        hb.setTitle(_("Help"))
        hb.l = l2 = QVBoxLayout(hb)
        self.help = h = QPlainTextEdit(self)
        l2.addWidget(h)
        h.setReadOnly(True)
        g.addWidget(hb, 1, 0, 1, 3)

        self.eb = eb = QGroupBox(self)
        g.addWidget(eb, 2, 0, 1, 3)
        eb.setTitle(_("Edit tweak"))
        eb.g = ebg = QGridLayout(eb)
        self.edit_tweak = et = QPlainTextEdit(self)
        et.setMinimumWidth(400)
        et.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
        ebg.addWidget(et, 0, 0, 1, 2)
        self.restore_default_button = b = QPushButton(self)
        b.setToolTip(_("Restore this tweak to its default value"))
        b.setText(_("&Reset this tweak"))
        ebg.addWidget(b, 1, 0, 1, 1)
        self.apply_button = ab = QPushButton(self)
        ab.setToolTip(_("Apply any changes you made to this tweak"))
        ab.setText(_("&Apply changes to this tweak"))
        ebg.addWidget(ab, 1, 1, 1, 1)
Exemple #23
0
    def setup_ui(self):  # {{{
        self._g = g = QHBoxLayout(self)
        self.setLayout(g)
        self._l = l = QVBoxLayout()
        g.addLayout(l)

        fmts = sorted(x.upper() for x in self.fmts)
        self.fmt_choice_box = QGroupBox(_('Choose the format to unpack:'), self)
        self._fl = fl = QHBoxLayout()
        self.fmt_choice_box.setLayout(self._fl)
        self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts]
        for x in self.fmt_choice_buttons:
            fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
                    0)
        l.addWidget(self.fmt_choice_box)
        self.fmt_choice_box.setVisible(len(fmts) > 1)

        self.help_label = QLabel(_('''\
            <h2>About Unpack book</h2>
            <p>Unpack book allows you to fine tune the appearance of an e-book by
            making small changes to its internals. In order to use Unpack book,
            you need to know a little bit about HTML and CSS, technologies that
            are used in e-books. Follow the steps:</p>
            <br>
            <ol>
            <li>Click "Explode book": This will "explode" the book into its
            individual internal components.<br></li>
            <li>Right click on any individual file and select "Open with..." to
            edit it in your favorite text editor.<br></li>
            <li>When you are done: <b>close the file browser window
            and the editor windows you used to make your tweaks</b>. Then click
            the "Rebuild book" button, to update the book in your calibre
            library.</li>
            </ol>'''))
        self.help_label.setWordWrap(True)
        self._fr = QFrame()
        self._fr.setFrameShape(QFrame.Shape.VLine)
        g.addWidget(self._fr)
        g.addWidget(self.help_label)

        self._b = b = QGridLayout()
        left, top, right, bottom = b.getContentsMargins()
        top += top
        b.setContentsMargins(left, top, right, bottom)
        l.addLayout(b, stretch=10)

        self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode book'))
        self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview book'))
        self.cancel_button  = QPushButton(QIcon(I('window-close.png')), _('&Cancel'))
        self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild book'))

        self.explode_button.setToolTip(
                _('Explode the book to edit its components'))
        self.preview_button.setToolTip(
                _('Preview the result of your changes'))
        self.cancel_button.setToolTip(
                _('Abort without saving any changes'))
        self.rebuild_button.setToolTip(
            _('Save your changes and update the book in the calibre library'))

        a = b.addWidget
        a(self.explode_button, 0, 0, 1, 1)
        a(self.preview_button, 0, 1, 1, 1)
        a(self.cancel_button,  1, 0, 1, 1)
        a(self.rebuild_button, 1, 1, 1, 1)

        for x in ('explode', 'preview', 'cancel', 'rebuild'):
            getattr(self, x+'_button').clicked.connect(getattr(self, x))

        self.msg = QLabel('dummy', self)
        self.msg.setVisible(False)
        self.msg.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
            font-size: x-large;
            font-weight: bold;
        }
        ''')

        self.resize(self.sizeHint() + QSize(40, 10))
 def __init__(self):
     QMainWindow.__init__(self)
     f = factory()
     self.setMinimumWidth(400)
     self.setWindowTitle('Demo of DBUS menu exporter and systray integration')
     self.statusBar().showMessage(self.windowTitle())
     w = QWidget(self)
     self.setCentralWidget(w)
     self.l = l = QVBoxLayout(w)
     mb = self.menu_bar = f.create_window_menubar(self)
     m = self.menu_one = mb.addMenu('&One')
     m.aboutToShow.connect(self.about_to_show_one)
     s = self.style()
     self.q = q = QAction('&Quit', self)
     q.setShortcut(QKeySequence.StandardKey.Quit), q.setIcon(s.standardIcon(QStyle.StandardPixmap.SP_DialogCancelButton))
     q.triggered.connect(QApplication.quit)
     self.addAction(q)
     QApplication.instance().setWindowIcon(s.standardIcon(QStyle.StandardPixmap.SP_ComputerIcon))
     for i, icon in zip(range(3), map(s.standardIcon, (
             QStyle.StandardPixmap.SP_DialogOkButton, QStyle.StandardPixmap.SP_DialogHelpButton, QStyle.StandardPixmap.SP_ArrowUp))):
         ac = m.addAction('One - &%d' % (i + 1))
         ac.setShortcut(QKeySequence(Qt.Modifier.CTRL | (Qt.Key.Key_1 + i), Qt.Modifier.SHIFT | (Qt.Key.Key_1 + i)))
         ac.setIcon(icon)
     m.addSeparator()
     self.menu_two = m2 = m.addMenu('A &submenu')
     for i, icon in zip(range(3), map(s.standardIcon, (
             QStyle.StandardPixmap.SP_DialogOkButton, QStyle.StandardPixmap.SP_DialogCancelButton, QStyle.StandardPixmap.SP_ArrowUp))):
         ac = m2.addAction('Two - &%d' % (i + 1))
         ac.setShortcut(QKeySequence(Qt.Modifier.CTRL | (Qt.Key.Key_A + i)))
         ac.setIcon(icon)
     m2.aboutToShow.connect(self.about_to_show_two)
     m2.addSeparator(), m.addSeparator()
     m.addAction('&Disabled action').setEnabled(False)
     ac = m.addAction('A checkable action')
     make_checkable(ac)
     g = QActionGroup(self)
     make_checkable(g.addAction(m.addAction('Exclusive 1')))
     make_checkable(g.addAction(m.addAction('Exclusive 2')), False)
     m.addSeparator()
     self.about_to_show_sentinel = m.addAction('This action\'s text should change before menu is shown')
     self.as_count = 0
     for ac in mb.findChildren(QAction):
         ac.triggered.connect(self.action_triggered)
     for m in mb.findChildren(QMenu):
         m.aboutToShow.connect(self.about_to_show)
     self.systray = f.create_system_tray_icon(parent=self, title=self.windowTitle())
     if self.systray is not None:
         self.systray.activated.connect(self.tray_activated)
         self.sm = m = QMenu()
         m.addAction('Show/hide main window').triggered.connect(self.tray_activated)
         m.addAction(q)
         self.systray.setContextMenu(m)
         self.update_tray_toggle_action()
         self.cib = b = QPushButton('Change system tray icon')
         l.addWidget(b), b.clicked.connect(self.change_icon)
         self.hib = b = QPushButton('Show/Hide system tray icon')
         l.addWidget(b), b.clicked.connect(self.systray.toggle)
         self.update_tooltip_timer = t = QTimer(self)
         t.setInterval(1000), t.timeout.connect(self.update_tooltip), t.start()
     self.ab = b = QPushButton('Add a new menu')
     b.clicked.connect(self.add_menu), l.addWidget(b)
     self.rb = b = QPushButton('Remove a created menu')
     b.clicked.connect(self.remove_menu), l.addWidget(b)
     self.sd = b = QPushButton('Show modal dialog')
     b.clicked.connect(self.show_dialog), l.addWidget(b)
     print('DBUS connection unique name:', f.bus.get_unique_name())
Exemple #25
0
    def ask_link(self):

        class Ask(QDialog):

            def accept(self):
                if self.treat_as_image.isChecked():
                    url = self.url.text()
                    if url.lower().split(':', 1)[0] in ('http', 'https'):
                        error_dialog(self, _('Remote images not supported'), _(
                            'You must download the image to your computer, URLs pointing'
                            ' to remote images are not supported.'), show=True)
                        return
                QDialog.accept(self)

        d = Ask(self)
        d.setWindowTitle(_('Create link'))
        l = QFormLayout()
        l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
        d.setLayout(l)
        d.url = QLineEdit(d)
        d.name = QLineEdit(d)
        d.treat_as_image = QCheckBox(d)
        d.setMinimumWidth(600)
        d.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel)
        d.br = b = QPushButton(_('&Browse'))
        b.setIcon(QIcon(I('document_open.png')))

        def cf():
            filetypes = []
            if d.treat_as_image.isChecked():
                filetypes = [(_('Images'), 'png jpeg jpg gif'.split())]
            files = choose_files(d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True)
            if files:
                path = files[0]
                d.url.setText(path)
                if path and os.path.exists(path):
                    with lopen(path, 'rb') as f:
                        q = what(f)
                    is_image = q in {'jpeg', 'png', 'gif'}
                    d.treat_as_image.setChecked(is_image)

        b.clicked.connect(cf)
        d.la = la = QLabel(_(
            'Enter a URL. If you check the "Treat the URL as an image" box '
            'then the URL will be added as an image reference instead of as '
            'a link. You can also choose to create a link to a file on '
            'your computer. '
            'Note that if you create a link to a file on your computer, it '
            'will stop working if the file is moved.'))
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-bottom: 1.5ex }')
        l.setWidget(0, QFormLayout.ItemRole.SpanningRole, la)
        l.addRow(_('Enter &URL:'), d.url)
        l.addRow(_('Treat the URL as an &image'), d.treat_as_image)
        l.addRow(_('Enter &name (optional):'), d.name)
        l.addRow(_('Choose a file on your computer:'), d.br)
        l.addRow(d.bb)
        d.bb.accepted.connect(d.accept)
        d.bb.rejected.connect(d.reject)
        d.resize(d.sizeHint())
        link, name, is_image = None, None, False
        if d.exec_() == QDialog.DialogCode.Accepted:
            link, name = unicode_type(d.url.text()).strip(), unicode_type(d.name.text()).strip()
            is_image = d.treat_as_image.isChecked()
        return link, name, is_image
Exemple #26
0
    def __init__(self, parent, prefs):
        QStackedWidget.__init__(self, parent)
        self.prefs = prefs
        self.setMinimumWidth(250)
        self.root_pane = rp = QWidget(self)
        self.item_pane = ip = QWidget(self)
        self.current_item = None
        sa = QScrollArea(self)
        sa.setWidgetResizable(True)
        sa.setWidget(rp)
        self.addWidget(sa)
        sa = QScrollArea(self)
        sa.setWidgetResizable(True)
        sa.setWidget(ip)
        self.addWidget(sa)

        self.l1 = la = QLabel('<p>' + _(
            'You can edit existing entries in the Table of Contents by clicking them'
            ' in the panel to the left.'
        ) + '<p>' + _(
            'Entries with a green tick next to them point to a location that has '
            'been verified to exist. Entries with a red dot are broken and may need'
            ' to be fixed.'))
        la.setStyleSheet('QLabel { margin-bottom: 20px }')
        la.setWordWrap(True)
        l = rp.l = QVBoxLayout()
        rp.setLayout(l)
        l.addWidget(la)
        self.add_new_to_root_button = b = QPushButton(_('Create a &new entry'))
        b.clicked.connect(self.add_new_to_root)
        l.addWidget(b)
        l.addStretch()

        self.cfmhb = b = QPushButton(_('Generate ToC from &major headings'))
        b.clicked.connect(self.create_from_major_headings)
        b.setToolTip(
            textwrap.fill(
                _('Generate a Table of Contents from the major headings in the book.'
                  ' This will work if the book identifies its headings using HTML'
                  ' heading tags. Uses the <h1>, <h2> and <h3> tags.')))
        l.addWidget(b)
        self.cfmab = b = QPushButton(_('Generate ToC from &all headings'))
        b.clicked.connect(self.create_from_all_headings)
        b.setToolTip(
            textwrap.fill(
                _('Generate a Table of Contents from all the headings in the book.'
                  ' This will work if the book identifies its headings using HTML'
                  ' heading tags. Uses the <h1-6> tags.')))
        l.addWidget(b)

        self.lb = b = QPushButton(_('Generate ToC from &links'))
        b.clicked.connect(self.create_from_links)
        b.setToolTip(
            textwrap.fill(
                _('Generate a Table of Contents from all the links in the book.'
                  ' Links that point to destinations that do not exist in the book are'
                  ' ignored. Also multiple links with the same destination or the same'
                  ' text are ignored.')))
        l.addWidget(b)

        self.cfb = b = QPushButton(_('Generate ToC from &files'))
        b.clicked.connect(self.create_from_files)
        b.setToolTip(
            textwrap.fill(
                _('Generate a Table of Contents from individual files in the book.'
                  ' Each entry in the ToC will point to the start of the file, the'
                  ' text of the entry will be the "first line" of text from the file.'
                  )))
        l.addWidget(b)

        self.xpb = b = QPushButton(_('Generate ToC from &XPath'))
        b.clicked.connect(self.create_from_user_xpath)
        b.setToolTip(
            textwrap.fill(
                _('Generate a Table of Contents from arbitrary XPath expressions.'
                  )))
        l.addWidget(b)

        self.fal = b = QPushButton(_('&Flatten the ToC'))
        b.clicked.connect(self.flatten_toc)
        b.setToolTip(
            textwrap.fill(
                _('Flatten the Table of Contents, putting all entries at the top level'
                  )))
        l.addWidget(b)

        l.addStretch()
        self.w1 = la = QLabel(
            _('<b>WARNING:</b> calibre only supports the '
              'creation of linear ToCs in AZW3 files. In a '
              'linear ToC every entry must point to a '
              'location after the previous entry. If you '
              'create a non-linear ToC it will be '
              'automatically re-arranged inside the AZW3 file.'))
        la.setWordWrap(True)
        l.addWidget(la)

        l = ip.l = QGridLayout()
        ip.setLayout(l)
        la = ip.heading = QLabel('')
        l.addWidget(la, 0, 0, 1, 2)
        la.setWordWrap(True)
        la = ip.la = QLabel(
            _('You can move this entry around the Table of Contents by drag '
              'and drop or using the up and down buttons to the left'))
        la.setWordWrap(True)
        l.addWidget(la, 1, 0, 1, 2)

        # Item status
        ip.hl1 = hl = QFrame()
        hl.setFrameShape(QFrame.Shape.HLine)
        l.addWidget(hl, l.rowCount(), 0, 1, 2)
        self.icon_label = QLabel()
        self.status_label = QLabel()
        self.status_label.setWordWrap(True)
        l.addWidget(self.icon_label, l.rowCount(), 0)
        l.addWidget(self.status_label, l.rowCount() - 1, 1)
        ip.hl2 = hl = QFrame()
        hl.setFrameShape(QFrame.Shape.HLine)
        l.addWidget(hl, l.rowCount(), 0, 1, 2)

        # Edit/remove item
        rs = l.rowCount()
        ip.b1 = b = QPushButton(QIcon(I('edit_input.png')),
                                _('Change the &location this entry points to'),
                                self)
        b.clicked.connect(self.edit_item)
        l.addWidget(b, l.rowCount() + 1, 0, 1, 2)
        ip.b2 = b = QPushButton(QIcon(I('trash.png')), _('&Remove this entry'),
                                self)
        l.addWidget(b, l.rowCount(), 0, 1, 2)
        b.clicked.connect(self.delete_item)
        ip.hl3 = hl = QFrame()
        hl.setFrameShape(QFrame.Shape.HLine)
        l.addWidget(hl, l.rowCount(), 0, 1, 2)
        l.setRowMinimumHeight(rs, 20)

        # Add new item
        rs = l.rowCount()
        ip.b3 = b = QPushButton(QIcon(I('plus.png')),
                                _('New entry &inside this entry'))
        connect_lambda(b.clicked, self, lambda self: self.add_new('inside'))
        l.addWidget(b, l.rowCount() + 1, 0, 1, 2)
        ip.b4 = b = QPushButton(QIcon(I('plus.png')),
                                _('New entry &above this entry'))
        connect_lambda(b.clicked, self, lambda self: self.add_new('before'))
        l.addWidget(b, l.rowCount(), 0, 1, 2)
        ip.b5 = b = QPushButton(QIcon(I('plus.png')),
                                _('New entry &below this entry'))
        connect_lambda(b.clicked, self, lambda self: self.add_new('after'))
        l.addWidget(b, l.rowCount(), 0, 1, 2)
        # Flatten entry
        ip.b3 = b = QPushButton(QIcon(I('heuristics.png')),
                                _('&Flatten this entry'))
        b.clicked.connect(self.flatten_item)
        b.setToolTip(
            _('All children of this entry are brought to the same '
              'level as this entry.'))
        l.addWidget(b, l.rowCount() + 1, 0, 1, 2)

        ip.hl4 = hl = QFrame()
        hl.setFrameShape(QFrame.Shape.HLine)
        l.addWidget(hl, l.rowCount(), 0, 1, 2)
        l.setRowMinimumHeight(rs, 20)

        # Return to welcome
        rs = l.rowCount()
        ip.b4 = b = QPushButton(QIcon(I('back.png')),
                                _('&Return to welcome screen'))
        b.clicked.connect(self.go_to_root)
        b.setToolTip(_('Go back to the top level view'))
        l.addWidget(b, l.rowCount() + 1, 0, 1, 2)

        l.setRowMinimumHeight(rs, 20)

        l.addWidget(QLabel(), l.rowCount(), 0, 1, 2)
        l.setColumnStretch(1, 10)
        l.setRowStretch(l.rowCount() - 1, 10)
        self.w2 = la = QLabel(self.w1.text())
        self.w2.setWordWrap(True)
        l.addWidget(la, l.rowCount(), 0, 1, 2)
Exemple #27
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.l = l = QGridLayout(self)
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        self.toc = parent.toc

        self.bookmarks_list = bl = BookmarksList(self)
        bl.itemChanged.connect(self.item_changed)
        l.addWidget(bl, 0, 0, 1, -1)
        bl.itemClicked.connect(self.item_activated)
        bl.bookmark_activated.connect(self.item_activated)
        bl.changed.connect(lambda: self.edited.emit(self.get_bookmarks()))
        bl.ac_edit.triggered.connect(self.edit_bookmark)
        bl.ac_delete.triggered.connect(self.delete_bookmark)

        self.la = la = QLabel(_('Double click to edit the bookmarks'))
        la.setWordWrap(True)
        l.addWidget(la, l.rowCount(), 0, 1, -1)

        self.button_new = b = QPushButton(QIcon(I('bookmarks.png')), _('&New'),
                                          self)
        b.clicked.connect(self.create_requested)
        b.setToolTip(_('Create a new bookmark at the current location'))
        l.addWidget(b)

        self.button_delete = b = QPushButton(QIcon(I('trash.png')),
                                             _('&Remove'), self)
        b.setToolTip(_('Remove the currently selected bookmark'))
        b.clicked.connect(self.delete_bookmark)
        l.addWidget(b, l.rowCount() - 1, 1)

        self.button_prev = b = QPushButton(QIcon(I('back.png')),
                                           _('Pre&vious'), self)
        b.clicked.connect(self.bookmarks_list.previous_bookmark)
        l.addWidget(b)

        self.button_next = b = QPushButton(QIcon(I('forward.png')), _('Nex&t'),
                                           self)
        b.clicked.connect(self.bookmarks_list.next_bookmark)
        l.addWidget(b, l.rowCount() - 1, 1)

        la = QLabel(_('&Sort by:'))
        self.sort_by = sb = QComboBox(self)
        la.setBuddy(sb)
        sb.addItem(_('Title'), 'title')
        sb.addItem(_('Position in book'), 'pos')
        sb.addItem(_('Date'), 'timestamp')
        sb.setToolTip(_('Change how the bookmarks are sorted'))
        i = sb.findData(vprefs['bookmarks_sort'])
        if i > -1:
            sb.setCurrentIndex(i)
        h = QHBoxLayout()
        h.addWidget(la), h.addWidget(sb, 10)
        l.addLayout(h, l.rowCount(), 0, 1, 2)
        sb.currentIndexChanged.connect(self.sort_by_changed)

        self.button_export = b = QPushButton(_('E&xport'), self)
        b.clicked.connect(self.export_bookmarks)
        l.addWidget(b, l.rowCount(), 0)

        self.button_import = b = QPushButton(_('&Import'), self)
        b.clicked.connect(self.import_bookmarks)
        l.addWidget(b, l.rowCount() - 1, 1)
Exemple #28
0
    def __init__(self, parent, prefs):
        QWidget.__init__(self, parent)
        self.toc_title = None
        self.prefs = prefs
        l = self.l = QGridLayout()
        self.setLayout(l)
        self.tocw = t = TreeWidget(self)
        self.tocw.edit_item.connect(self.edit_item)
        l.addWidget(t, 0, 0, 7, 3)
        self.up_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-up.png')))
        b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        l.addWidget(b, 0, 3)
        b.setToolTip(_('Move current entry up [Ctrl+Up]'))
        b.clicked.connect(self.move_up)

        self.left_button = b = QToolButton(self)
        b.setIcon(QIcon(I('back.png')))
        b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        l.addWidget(b, 2, 3)
        b.setToolTip(_('Unindent the current entry [Ctrl+Left]'))
        b.clicked.connect(self.tocw.move_left)

        self.del_button = b = QToolButton(self)
        b.setIcon(QIcon(I('trash.png')))
        b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        l.addWidget(b, 3, 3)
        b.setToolTip(_('Remove all selected entries'))
        b.clicked.connect(self.del_items)

        self.right_button = b = QToolButton(self)
        b.setIcon(QIcon(I('forward.png')))
        b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        l.addWidget(b, 4, 3)
        b.setToolTip(_('Indent the current entry [Ctrl+Right]'))
        b.clicked.connect(self.tocw.move_right)

        self.down_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-down.png')))
        b.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        l.addWidget(b, 6, 3)
        b.setToolTip(_('Move current entry down [Ctrl+Down]'))
        b.clicked.connect(self.move_down)
        self.expand_all_button = b = QPushButton(_('&Expand all'))
        col = 7
        l.addWidget(b, col, 0)
        b.clicked.connect(self.tocw.expandAll)
        self.collapse_all_button = b = QPushButton(_('&Collapse all'))
        b.clicked.connect(self.tocw.collapseAll)
        l.addWidget(b, col, 1)
        self.default_msg = _('Double click on an entry to change the text')
        self.hl = hl = QLabel(self.default_msg)
        hl.setSizePolicy(QSizePolicy.Policy.Ignored,
                         QSizePolicy.Policy.Ignored)
        l.addWidget(hl, col, 2, 1, -1)
        self.item_view = i = ItemView(self, self.prefs)
        self.item_view.delete_item.connect(self.delete_current_item)
        i.add_new_item.connect(self.add_new_item)
        i.create_from_xpath.connect(self.create_from_xpath)
        i.create_from_links.connect(self.create_from_links)
        i.create_from_files.connect(self.create_from_files)
        i.flatten_item.connect(self.flatten_item)
        i.flatten_toc.connect(self.flatten_toc)
        i.go_to_root.connect(self.go_to_root)
        l.addWidget(i, 0, 4, col, 1)

        l.setColumnStretch(2, 10)
Exemple #29
0
class Preferences(QDialog):

    run_wizard_requested = pyqtSignal()

    def __init__(self, gui, initial_plugin=None, close_after_initial=False):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.must_restart = False
        self.do_restart = False
        self.committed = False
        self.close_after_initial = close_after_initial

        self.resize(930, 720)
        nh, nw = min_available_height() - 25, available_width() - 10
        if nh < 0:
            nh = 800
        if nw < 0:
            nw = 600
        nh = min(self.height(), nh)
        nw = min(self.width(), nw)
        self.resize(nw, nh)

        geom = gprefs.get('preferences dialog geometry', None)
        if geom is not None:
            QApplication.instance().safe_restore_geometry(self, geom)

        # Center
        if islinux:
            self.move(gui.rect().center() - self.rect().center())

        self.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.setWindowTitle(__appname__ + ' — ' + _('Preferences'))
        self.setWindowIcon(QIcon(I('config.png')))
        self.l = l = QVBoxLayout(self)

        self.stack = QStackedWidget(self)
        self.bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Close
            | QDialogButtonBox.StandardButton.Apply
            | QDialogButtonBox.StandardButton.Cancel
            | QDialogButtonBox.StandardButton.RestoreDefaults)
        self.bb.button(QDialogButtonBox.StandardButton.Apply).clicked.connect(
            self.accept)
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).setIcon(
                QIcon(I('clear_left.png')))
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).clicked.connect(
                self.restore_defaults)
        self.wizard_button = QPushButton(QIcon(I('wizard.png')),
                                         _('Run Welcome &wizard'))
        self.wizard_button.clicked.connect(
            self.run_wizard, type=Qt.ConnectionType.QueuedConnection)
        self.wizard_button.setAutoDefault(False)
        self.bb.rejected.connect(self.reject)
        self.browser = Browser(self)
        self.browser.show_plugin.connect(self.show_plugin)
        self.stack.addWidget(self.browser)
        self.scroll_area = QScrollArea(self)
        self.stack.addWidget(self.scroll_area)
        self.scroll_area.setWidgetResizable(True)

        self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
        self.title_bar = TitleBar(self)
        for ac, tt in [(QDialogButtonBox.StandardButton.Apply,
                        _('Save changes')),
                       (QDialogButtonBox.StandardButton.Cancel,
                        _('Cancel and return to overview'))]:
            self.bb.button(ac).setToolTip(tt)

        l.addWidget(self.title_bar), l.addWidget(self.stack)
        h = QHBoxLayout()
        l.addLayout(h)
        h.addWidget(self.wizard_button), h.addStretch(10), h.addWidget(self.bb)

        if initial_plugin is not None:
            category, name = initial_plugin[:2]
            plugin = get_plugin(category, name)
            if plugin is not None:
                self.show_plugin(plugin)
                if len(initial_plugin) > 2:
                    w = self.findChild(QWidget, initial_plugin[2])
                    if w is not None:
                        for c in self.showing_widget.children():
                            if isinstance(c, QTabWidget):
                                idx = c.indexOf(w)
                                if idx > -1:
                                    c.setCurrentIndex(idx)
                                    break
        else:
            self.hide_plugin()

    def event(self, ev):
        if isinstance(ev, QStatusTipEvent):
            msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip())
            self.title_bar.show_msg(msg)
        return QDialog.event(self, ev)

    def run_wizard(self):
        self.run_wizard_requested.emit()
        self.accept()

    def set_tooltips_for_labels(self):
        def process_child(child):
            for g in child.children():
                if isinstance(g, QLabel):
                    buddy = g.buddy()
                    if buddy is not None and hasattr(buddy, 'toolTip'):
                        htext = str(buddy.toolTip()).strip()
                        etext = str(g.toolTip()).strip()
                        if htext and not etext:
                            g.setToolTip(htext)
                            g.setWhatsThis(htext)
                else:
                    process_child(g)

        process_child(self.showing_widget)

    def show_plugin(self, plugin):
        self.showing_widget = plugin.create_widget(self.scroll_area)
        self.showing_widget.genesis(self.gui)
        try:
            self.showing_widget.initialize()
        except AbortInitialize:
            return
        self.set_tooltips_for_labels()
        self.scroll_area.setWidget(self.showing_widget)
        self.stack.setCurrentIndex(1)
        self.showing_widget.show()
        self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' +
                            plugin.gui_name)
        self.showing_widget.restart_now.connect(self.restart_now)
        self.title_bar.show_plugin(plugin)
        self.setWindowIcon(QIcon(plugin.icon))

        self.bb.button(QDialogButtonBox.StandardButton.Close).setVisible(False)
        self.wizard_button.setVisible(False)
        for button in (QDialogButtonBox.StandardButton.Apply,
                       QDialogButtonBox.StandardButton.RestoreDefaults,
                       QDialogButtonBox.StandardButton.Cancel):
            button = self.bb.button(button)
            button.setVisible(True)

        self.bb.button(QDialogButtonBox.StandardButton.Apply).setEnabled(False)
        self.bb.button(QDialogButtonBox.StandardButton.Apply).setDefault(
            False), self.bb.button(
                QDialogButtonBox.StandardButton.Apply).setDefault(True)
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).setEnabled(
                self.showing_widget.supports_restoring_to_defaults)
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).setToolTip(
                self.showing_widget.restore_defaults_desc if self.
                showing_widget.supports_restoring_to_defaults else (
                    _('Restoring to defaults not supported for') + ' ' +
                    plugin.gui_name))
        self.bb.button(
            QDialogButtonBox.StandardButton.RestoreDefaults).setText(
                _('Restore &defaults'))
        self.showing_widget.changed_signal.connect(self.changed_signal)

    def changed_signal(self):
        b = self.bb.button(QDialogButtonBox.StandardButton.Apply)
        b.setEnabled(True)

    def hide_plugin(self):
        for sig in 'changed_signal restart_now'.split():
            try:
                getattr(self.showing_widget,
                        sig).disconnect(getattr(self, sig))
            except Exception:
                pass
        self.showing_widget = QWidget(self.scroll_area)
        self.scroll_area.setWidget(self.showing_widget)
        self.setWindowTitle(__appname__ + ' - ' + _('Preferences'))
        self.stack.setCurrentIndex(0)
        self.title_bar.show_plugin()
        self.setWindowIcon(QIcon(I('config.png')))

        for button in (QDialogButtonBox.StandardButton.Apply,
                       QDialogButtonBox.StandardButton.RestoreDefaults,
                       QDialogButtonBox.StandardButton.Cancel):
            button = self.bb.button(button)
            button.setVisible(False)

        self.bb.button(QDialogButtonBox.StandardButton.Close).setVisible(True)
        self.bb.button(QDialogButtonBox.StandardButton.Close).setDefault(
            False), self.bb.button(
                QDialogButtonBox.StandardButton.Close).setDefault(True)
        self.wizard_button.setVisible(True)

    def restart_now(self):
        try:
            self.showing_widget.commit()
        except AbortCommit:
            return
        self.do_restart = True
        self.hide_plugin()
        self.accept()

    def commit(self, *args):
        must_restart = self.showing_widget.commit()
        rc = self.showing_widget.restart_critical
        self.committed = True
        do_restart = False
        if must_restart:
            self.must_restart = True
            msg = _('Some of the changes you made require a restart.'
                    ' Please restart calibre as soon as possible.')
            if rc:
                msg = _('The changes you have made require calibre be '
                        'restarted immediately. You will not be allowed to '
                        'set any more preferences, until you restart.')

            do_restart = show_restart_warning(msg, parent=self)

        self.showing_widget.refresh_gui(self.gui)
        if do_restart:
            self.do_restart = True
        return self.close_after_initial or (must_restart and rc) or do_restart

    def restore_defaults(self, *args):
        self.showing_widget.restore_defaults()

    def on_shutdown(self):
        gprefs.set('preferences dialog geometry',
                   bytearray(self.saveGeometry()))
        if self.committed:
            self.gui.must_restart_before_config = self.must_restart
            self.gui.tags_view.recount()
            self.gui.create_device_menu()
            self.gui.set_device_menu_items_state(
                bool(self.gui.device_connected))
            self.gui.bars_manager.apply_settings()
            self.gui.bars_manager.update_bars()
            self.gui.build_context_menus()

    def accept(self):
        if self.stack.currentIndex() == 0:
            self.on_shutdown()
            return QDialog.accept(self)
        try:
            close = self.commit()
        except AbortCommit:
            return
        if close:
            self.on_shutdown()
            return QDialog.accept(self)
        self.hide_plugin()

    def reject(self):
        if self.stack.currentIndex() == 0 or self.close_after_initial:
            self.on_shutdown()
            return QDialog.reject(self)
        self.hide_plugin()
Exemple #30
0
    def __init__(self,
                 mi=None,
                 prefs=None,
                 parent=None,
                 for_global_prefs=False):
        QWidget.__init__(self, parent)
        self.ignore_changed = False
        self.for_global_prefs = for_global_prefs

        self.l = l = QHBoxLayout(self)
        l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(l)
        self.settings_tabs = st = QTabWidget(self)
        l.addWidget(st)
        self.preview_label = la = Preview(self)
        l.addWidget(la)

        if prefs is None:
            prefs = cprefs
        self.original_prefs = prefs
        self.mi = mi or self.default_mi()

        self.colors_page = cp = QWidget(st)
        st.addTab(cp, _('&Colors'))
        cp.l = l = QGridLayout()
        cp.setLayout(l)
        if for_global_prefs:
            msg = _(
                'When generating covers, a color scheme for the cover is chosen at random from the'
                ' color schemes below. You can prevent an individual scheme from being selected by'
                ' unchecking it. The preview on the right shows the currently selected color scheme.'
            )
        else:
            msg = _(
                'Choose a color scheme to be used for this generated cover.'
            ) + '<p>' + _(
                'In normal cover generation, the color scheme is chosen at random from the list of color schemes below. You'
                ' can prevent an individual color scheme from being chosen by unchecking it here.'
            )
        cp.la = la = QLabel('<p>' + msg)
        la.setWordWrap(True)
        l.addWidget(la, 0, 0, 1, -1)
        self.colors_list = cl = QListWidget(cp)
        l.addWidget(cl, 1, 0, 1, -1)
        self.colors_map = OrderedDict()
        self.ncs = ncs = QPushButton(QIcon(I('plus.png')),
                                     _('&New color scheme'), cp)
        ncs.clicked.connect(self.create_color_scheme)
        l.addWidget(ncs)
        self.ecs = ecs = QPushButton(QIcon(I('format-fill-color.png')),
                                     _('&Edit color scheme'), cp)
        ecs.clicked.connect(self.edit_color_scheme)
        l.addWidget(ecs, l.rowCount() - 1, 1)
        self.rcs = rcs = QPushButton(QIcon(I('minus.png')),
                                     _('&Remove color scheme'), cp)
        rcs.clicked.connect(self.remove_color_scheme)
        l.addWidget(rcs, l.rowCount() - 1, 2)

        self.styles_page = sp = QWidget(st)
        st.addTab(sp, _('&Styles'))
        sp.l = l = QVBoxLayout()
        sp.setLayout(l)
        if for_global_prefs:
            msg = _(
                'When generating covers, a style for the cover is chosen at random from the'
                ' styles below. You can prevent an individual style from being selected by'
                ' unchecking it. The preview on the right shows the currently selected style.'
            )
        else:
            msg = _(
                'Choose a style to be used for this generated cover.'
            ) + '<p>' + _(
                'In normal cover generation, the style is chosen at random from the list of styles below. You'
                ' can prevent an individual style from being chosen by unchecking it here.'
            )
        sp.la = la = QLabel('<p>' + msg)
        la.setWordWrap(True)
        l.addWidget(la)
        self.styles_list = sl = QListWidget(sp)
        l.addWidget(sl)
        self.style_map = OrderedDict()

        self.font_page = fp = QWidget(st)
        st.addTab(fp, _('&Fonts and sizes'))
        fp.l = l = QFormLayout()
        fp.setLayout(l)
        fp.f = []

        def add_hline():
            f = QFrame()
            fp.f.append(f)
            f.setFrameShape(QFrame.Shape.HLine)
            l.addRow(f)

        for x, label, size_label in (
            ('title', _('&Title font family:'), _('&Title font size:')),
            ('subtitle', _('&Subtitle font family:'),
             _('&Subtitle font size:')),
            ('footer', _('&Footer font family:'), _('&Footer font size:')),
        ):
            attr = '%s_font_family' % x
            ff = FontFamilyChooser(fp)
            setattr(self, attr, ff)
            l.addRow(label, ff)
            ff.family_changed.connect(self.emit_changed)
            attr = '%s_font_size' % x
            fs = QSpinBox(fp)
            setattr(self, attr, fs)
            fs.setMinimum(8), fs.setMaximum(200), fs.setSuffix(' px')
            fs.setValue(prefs[attr])
            fs.valueChanged.connect(self.emit_changed)
            l.addRow(size_label, fs)
            add_hline()
        self.changed_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(500), t.timeout.connect(
            self.emit_changed)

        def create_sz(label):
            ans = QSpinBox(self)
            ans.setSuffix(' px'), ans.setMinimum(100), ans.setMaximum(10000)
            l.addRow(label, ans)
            ans.valueChanged.connect(self.changed_timer.start)
            return ans

        self.cover_width = create_sz(_('Cover &width:'))
        self.cover_height = create_sz(_('Cover &height:'))
        fp.cla = la = QLabel(
            _('Note that the preview to the side is of fixed aspect ratio, so changing the cover'
              ' width above will not have any effect. If you change the height, you should also change the width nevertheless'
              ' as it will be used in actual cover generation.'))
        la.setWordWrap(True)
        l.addRow(la)

        self.templates_page = tp = QWidget(st)
        st.addTab(tp, _('&Text'))
        tp.l = l = QVBoxLayout()
        tp.setLayout(l)
        tp.la = la = QLabel(
            _('The text on the generated cover is taken from the metadata of the book.'
              ' This is controlled via templates. You can use the <b>, <i> and <br> tags'
              ' in the templates for bold, italic and line breaks, respectively. The'
              ' default templates use the title, series and authors. You can change them to use'
              ' whatever metadata you like.'))
        la.setWordWrap(True), la.setTextFormat(Qt.TextFormat.PlainText)
        l.addWidget(la)

        def create_template_widget(title, which, button):
            attr = which + '_template'
            heading = QLabel('<h2>' + title)
            setattr(tp, attr + '_heading', heading)
            l.addWidget(heading)
            la = QLabel()
            setattr(self, attr, la)
            l.addWidget(la), la.setTextFormat(
                Qt.TextFormat.PlainText), la.setStyleSheet(
                    'QLabel {font-family: monospace}')
            la.setWordWrap(True)
            b = QPushButton(button)
            b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
            connect_lambda(b.clicked, self,
                           lambda self: self.change_template(which))
            setattr(self, attr + '_button', b)
            l.addWidget(b)
            if which != 'footer':
                f = QFrame(tp)
                setattr(tp, attr + '_sep',
                        f), f.setFrameShape(QFrame.Shape.HLine)
                l.addWidget(f)
            l.addSpacing(10)

        create_template_widget(_('The title template'), 'title',
                               _('Change the &title template'))
        create_template_widget(_('The sub-title template'), 'subtitle',
                               _('Change the &sub-title template'))
        create_template_widget(_('The footer template'), 'footer',
                               _('Change the &footer template'))
        l.addStretch(2)

        self.apply_prefs(prefs)
        self.changed.connect(self.update_preview)
        self.styles_list.itemSelectionChanged.connect(self.update_preview)
        self.colors_list.itemSelectionChanged.connect(self.update_preview)
        self.update_preview()