コード例 #1
0
ファイル: to_zip.py プロジェクト: qykth-git/calibre
    def do_user_config(self, parent=None):
        '''
        This method shows a configuration dialog for this plugin. It returns
        True if the user clicks OK, False otherwise. The changes are
        automatically applied.
        '''
        from qt.core import (QDialog, QDialogButtonBox, QVBoxLayout, QLabel,
                             Qt, QLineEdit, QCheckBox)

        config_dialog = QDialog(parent)
        button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok
                                      | QDialogButtonBox.StandardButton.Cancel)
        v = QVBoxLayout(config_dialog)

        def size_dialog():
            config_dialog.resize(config_dialog.sizeHint())

        button_box.accepted.connect(config_dialog.accept)
        button_box.rejected.connect(config_dialog.reject)
        config_dialog.setWindowTitle(_('Customize') + ' ' + self.name)
        from calibre.customize.ui import (plugin_customization,
                                          customize_plugin)
        help_text = self.customization_help(gui=True)
        help_text = QLabel(help_text, config_dialog)
        help_text.setWordWrap(True)
        help_text.setTextInteractionFlags(
            Qt.TextInteractionFlag.LinksAccessibleByMouse
            | Qt.TextInteractionFlag.LinksAccessibleByKeyboard)
        help_text.setOpenExternalLinks(True)
        v.addWidget(help_text)
        bf = QCheckBox(_('Add linked files in breadth first order'))
        bf.setToolTip(
            _('Normally, when following links in HTML files'
              ' calibre does it depth first, i.e. if file A links to B and '
              ' C, but B links to D, the files are added in the order A, B, D, C. '
              ' With this option, they will instead be added as A, B, C, D'))
        sc = plugin_customization(self)
        if not sc:
            sc = ''
        sc = sc.strip()
        enc = sc.partition('|')[0]
        bfs = sc.partition('|')[-1]
        bf.setChecked(bfs == 'bf')
        sc = QLineEdit(enc, config_dialog)
        v.addWidget(sc)
        v.addWidget(bf)
        v.addWidget(button_box)
        size_dialog()
        config_dialog.exec()

        if config_dialog.result() == QDialog.DialogCode.Accepted:
            sc = str(sc.text()).strip()
            if bf.isChecked():
                sc += '|bf'
            customize_plugin(self, sc)

        return config_dialog.result()
コード例 #2
0
class Choose(QDialog):
    def __init__(self, fmts, parent=None):
        QDialog.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        self.setWindowTitle(_('Choose format to edit'))

        self.la = la = QLabel(
            _('This book has multiple formats that can be edited. Choose the format you want to edit.'
              ))
        l.addWidget(la)

        self.rem = QCheckBox(
            _('Always ask when more than one format is available'))
        self.rem.setChecked(True)
        l.addWidget(self.rem)

        self.bb = bb = QDialogButtonBox(self)
        l.addWidget(bb)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        self.buts = buts = []
        for fmt in fmts:
            b = bb.addButton(fmt.upper(),
                             QDialogButtonBox.ButtonRole.AcceptRole)
            b.setObjectName(fmt)
            connect_lambda(
                b.clicked, self,
                lambda self: self.chosen(self.sender().objectName()))
            buts.append(b)

        self.fmt = None
        self.resize(self.sizeHint())

    def chosen(self, fmt):
        self.fmt = fmt

    def accept(self):
        from calibre.gui2.tweak_book import tprefs
        tprefs['choose_tweak_fmt'] = self.rem.isChecked()
        QDialog.accept(self)
コード例 #3
0
class UpdateNotification(QDialog):
    def __init__(self, calibre_version, plugin_updates, parent=None):
        QDialog.__init__(self, parent)
        self.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
        self.resize(400, 250)
        self.l = QGridLayout()
        self.setLayout(self.l)
        self.logo = QLabel()
        self.logo.setMaximumWidth(110)
        self.logo.setPixmap(QIcon(I('lt.png')).pixmap(100, 100))
        ver = calibre_version
        if ver.endswith('.0'):
            ver = ver[:-2]
        self.label = QLabel(
            '<p>' +
            _('New version <b>{ver}</b> of {app} is available for download. '
              'See the <a href="{url}">new features</a>.').format(
                  url=localize_website_link(
                      'https://calibre-ebook.com/whats-new'),
                  app=__appname__,
                  ver=ver))
        self.label.setOpenExternalLinks(True)
        self.label.setWordWrap(True)
        self.setWindowTitle(_('Update available!'))
        self.setWindowIcon(QIcon(I('lt.png')))
        self.l.addWidget(self.logo, 0, 0)
        self.l.addWidget(self.label, 0, 1)
        self.cb = QCheckBox(_('Show this notification for future updates'),
                            self)
        self.l.addWidget(self.cb, 1, 0, 1, -1)
        self.cb.setChecked(config.get('new_version_notification'))
        self.cb.stateChanged.connect(self.show_future)
        self.bb = QDialogButtonBox(self)
        b = self.bb.addButton(_('&Get update'),
                              QDialogButtonBox.ButtonRole.AcceptRole)
        b.setDefault(True)
        b.setIcon(QIcon(I('arrow-down.png')))
        if plugin_updates > 0:
            b = self.bb.addButton(_('Update &plugins'),
                                  QDialogButtonBox.ButtonRole.ActionRole)
            b.setIcon(QIcon(I('plugins/plugin_updater.png')))
            b.clicked.connect(self.get_plugins,
                              type=Qt.ConnectionType.QueuedConnection)
        self.bb.addButton(QDialogButtonBox.StandardButton.Cancel)
        self.l.addWidget(self.bb, 2, 0, 1, -1)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        save_version_notified(calibre_version)

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

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

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

        QDialog.accept(self)
コード例 #4
0
class ProceedQuestion(QWidget):

    ask_question = pyqtSignal(object, object, object)

    @pyqtProperty(float)
    def show_fraction(self):
        return self._show_fraction

    @show_fraction.setter
    def show_fraction(self, val):
        self._show_fraction = max(0, min(1, float(val)))
        self.update()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setVisible(False)
        parent.installEventFilter(self)

        self._show_fraction = 0.0
        self.show_animation = a = QPropertyAnimation(self, b"show_fraction",
                                                     self)
        a.setDuration(1000), a.setEasingCurve(QEasingCurve.Type.OutQuad)
        a.setStartValue(0.0), a.setEndValue(1.0)
        a.finished.connect(self.stop_show_animation)
        self.rendered_pixmap = None

        self.questions = []

        self.icon = ic = Icon(self)
        self.msg_label = msg = QLabel('some random filler text')
        msg.setWordWrap(True)
        self.bb = QDialogButtonBox()
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.log_button = self.bb.addButton(
            _('View log'), QDialogButtonBox.ButtonRole.ActionRole)
        self.log_button.setIcon(QIcon(I('debug.png')))
        self.log_button.clicked.connect(self.show_log)
        self.copy_button = self.bb.addButton(
            _('&Copy to clipboard'), QDialogButtonBox.ButtonRole.ActionRole)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.action_button = self.bb.addButton(
            '', QDialogButtonBox.ButtonRole.ActionRole)
        self.action_button.clicked.connect(self.action_clicked)
        self.show_det_msg = _('Show &details')
        self.hide_det_msg = _('Hide &details')
        self.det_msg_toggle = self.bb.addButton(
            self.show_det_msg, QDialogButtonBox.ButtonRole.ActionRole)
        self.det_msg_toggle.clicked.connect(self.toggle_det_msg)
        self.det_msg_toggle.setToolTip(
            _('Show detailed information about this error'))
        self.det_msg = PlainTextEdit(self)
        self.det_msg.setReadOnly(True)
        self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Yes
                                   | QDialogButtonBox.StandardButton.No
                                   | QDialogButtonBox.StandardButton.Ok)
        self.bb.button(QDialogButtonBox.StandardButton.Yes).setDefault(True)
        self.title_label = title = QLabel('A dummy title')
        f = title.font()
        f.setBold(True)
        title.setFont(f)

        self.checkbox = QCheckBox('', self)

        self._l = l = QVBoxLayout(self)
        self._h = h = QHBoxLayout()
        self._v = v = QVBoxLayout()
        v.addWidget(title), v.addWidget(msg)
        h.addWidget(ic), h.addSpacing(10), h.addLayout(v), l.addLayout(h)
        l.addSpacing(5)
        l.addWidget(self.checkbox)
        l.addWidget(self.det_msg)
        l.addWidget(self.bb)

        self.ask_question.connect(self.do_ask_question,
                                  type=Qt.ConnectionType.QueuedConnection)
        self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        for child in self.findChildren(QWidget):
            child.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self.setFocusProxy(self.parent())
        self.resize_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(100), t.timeout.connect(
            self.parent_resized)

    def eventFilter(self, obj, ev):
        if ev.type() == QEvent.Type.Resize and self.isVisible():
            self.resize_timer.start()
        return False

    def parent_resized(self):
        if self.isVisible():
            self.do_resize()

    def copy_to_clipboard(self, *args):
        QApplication.clipboard().setText(
            'calibre, version %s\n%s: %s\n\n%s' %
            (__version__, unicode_type(
                self.windowTitle()), unicode_type(self.msg_label.text()),
             unicode_type(self.det_msg.toPlainText())))
        self.copy_button.setText(_('Copied'))

    def action_clicked(self):
        if self.questions:
            q = self.questions[0]
            self.questions[0] = q._replace(callback=q.action_callback)
        self.accept()

    def accept(self):
        if self.questions:
            payload, callback, cancel_callback = self.questions[0][:3]
            self.questions = self.questions[1:]
            cb = None
            if self.checkbox.isVisible():
                cb = bool(self.checkbox.isChecked())
            self.ask_question.emit(callback, payload, cb)
        self.hide()

    def reject(self):
        if self.questions:
            payload, callback, cancel_callback = self.questions[0][:3]
            self.questions = self.questions[1:]
            cb = None
            if self.checkbox.isVisible():
                cb = bool(self.checkbox.isChecked())
            self.ask_question.emit(cancel_callback, payload, cb)
        self.hide()

    def do_ask_question(self, callback, payload, checkbox_checked):
        if callable(callback):
            args = [payload]
            if checkbox_checked is not None:
                args.append(checkbox_checked)
            callback(*args)
        self.show_question()

    def toggle_det_msg(self, *args):
        vis = unicode_type(self.det_msg_toggle.text()) == self.hide_det_msg
        self.det_msg_toggle.setText(
            self.show_det_msg if vis else self.hide_det_msg)
        self.det_msg.setVisible(not vis)
        self.do_resize()

    def do_resize(self):
        sz = self.sizeHint()
        sz.setWidth(min(self.parent().width(), sz.width()))
        sb = self.parent().statusBar().height() + 10
        sz.setHeight(min(self.parent().height() - sb, sz.height()))
        self.resize(sz)
        self.position_widget()

    def show_question(self):
        if not self.questions:
            return
        if not self.isVisible():
            question = self.questions[0]
            self.msg_label.setText(question.msg)
            self.icon.set_icon(question.icon)
            self.title_label.setText(question.title)
            self.log_button.setVisible(bool(question.html_log))
            self.copy_button.setText(_('&Copy to clipboard'))
            if question.action_callback is not None:
                self.action_button.setText(question.action_label or '')
                self.action_button.setIcon(QIcon(
                ) if question.action_icon is None else question.action_icon)
            # Force the button box to relayout its buttons, as button text
            # might have changed
            self.bb.setOrientation(
                Qt.Orientation.Vertical), self.bb.setOrientation(
                    Qt.Orientation.Horizontal)
            self.det_msg.setPlainText(question.det_msg or '')
            self.det_msg.setVisible(False)
            self.det_msg_toggle.setVisible(bool(question.det_msg))
            self.det_msg_toggle.setText(self.show_det_msg)
            self.checkbox.setVisible(question.checkbox_msg is not None)
            if question.checkbox_msg is not None:
                self.checkbox.setText(question.checkbox_msg)
                self.checkbox.setChecked(question.checkbox_checked)
            self.bb.button(QDialogButtonBox.StandardButton.Ok).setVisible(
                question.show_ok)
            self.bb.button(QDialogButtonBox.StandardButton.Yes).setVisible(
                not question.show_ok)
            self.bb.button(QDialogButtonBox.StandardButton.No).setVisible(
                not question.show_ok)
            self.copy_button.setVisible(bool(question.show_copy_button))
            self.action_button.setVisible(question.action_callback is not None)
            self.toggle_det_msg() if question.show_det else self.do_resize()
            self.show_widget()
            button = self.action_button if question.focus_action and question.action_callback is not None else \
                (self.bb.button(QDialogButtonBox.StandardButton.Ok) if question.show_ok else self.bb.button(QDialogButtonBox.StandardButton.Yes))
            button.setDefault(True)
            self.raise_()
            self.start_show_animation()

    def start_show_animation(self):
        if self.rendered_pixmap is not None:
            return

        dpr = getattr(self, 'devicePixelRatioF', self.devicePixelRatio)()
        p = QImage(dpr * self.size(),
                   QImage.Format.Format_ARGB32_Premultiplied)
        p.setDevicePixelRatio(dpr)
        # For some reason, Qt scrolls the book view when rendering this widget,
        # for the very first time, so manually preserve its position
        pr = getattr(self.parent(), 'library_view', None)
        if not hasattr(pr, 'preserve_state'):
            self.render(p)
        else:
            with pr.preserve_state():
                self.render(p)
        self.rendered_pixmap = QPixmap.fromImage(p)
        self.original_visibility = v = []
        for child in self.findChildren(QWidget):
            if child.isVisible():
                child.setVisible(False)
                v.append(child)
        self.show_animation.start()

    def stop_show_animation(self):
        self.rendered_pixmap = None
        [c.setVisible(True) for c in getattr(self, 'original_visibility', ())]
        self.update()
        for child in self.findChildren(QWidget):
            child.update()
            if hasattr(child, 'viewport'):
                child.viewport().update()

    def position_widget(self):
        geom = self.parent().geometry()
        x = geom.width() - self.width() - 5
        sb = self.parent().statusBar()
        if sb is None:
            y = geom.height() - self.height()
        else:
            y = sb.geometry().top() - self.height()
        self.move(x, y)

    def show_widget(self):
        self.show()
        self.position_widget()

    def dummy_question(self, action_label=None):
        self(
            lambda *args: args, (),
            'dummy log',
            'Log Viewer',
            'A Dummy Popup',
            'This is a dummy popup to easily test things, with a long line of text that should wrap. '
            'This is a dummy popup to easily test things, with a long line of text that should wrap',
            checkbox_msg='A dummy checkbox',
            action_callback=lambda *args: args,
            action_label=action_label or 'An action')

    def __call__(self,
                 callback,
                 payload,
                 html_log,
                 log_viewer_title,
                 title,
                 msg,
                 det_msg='',
                 show_copy_button=False,
                 cancel_callback=None,
                 log_is_file=False,
                 checkbox_msg=None,
                 checkbox_checked=False,
                 action_callback=None,
                 action_label=None,
                 action_icon=None,
                 focus_action=False,
                 show_det=False,
                 show_ok=False,
                 icon=None,
                 log_viewer_unique_name=None,
                 **kw):
        '''
        A non modal popup that notifies the user that a background task has
        been completed. This class guarantees that only a single popup is
        visible at any one time. Other requests are queued and displayed after
        the user dismisses the current popup.

        :param callback: A callable that is called with payload if the user
        asks to proceed. Note that this is always called in the GUI thread.
        :param cancel_callback: A callable that is called with the payload if
        the users asks not to proceed.
        :param payload: Arbitrary object, passed to callback
        :param html_log: An HTML or plain text log
        :param log_viewer_title: The title for the log viewer window
        :param title: The title for this popup
        :param msg: The msg to display
        :param det_msg: Detailed message
        :param log_is_file: If True the html_log parameter is interpreted as
                            the path to a file on disk containing the log
                            encoded with utf-8
        :param checkbox_msg: If not None, a checkbox is displayed in the
                             dialog, showing this message. The callback is
                             called with both the payload and the state of the
                             checkbox as arguments.
        :param checkbox_checked: If True the checkbox is checked by default.
        :param action_callback: If not None, an extra button is added, which
                                when clicked will cause action_callback to be called
                                instead of callback. action_callback is called in
                                exactly the same way as callback.
        :param action_label: The text on the action button
        :param action_icon: The icon for the action button, must be a QIcon object or None
        :param focus_action: If True, the action button will be focused instead of the Yes button
        :param show_det: If True, the Detailed message will be shown initially
        :param show_ok: If True, OK will be shown instead of YES/NO
        :param icon: The icon to be used for this popop (defaults to question mark). Can be either a QIcon or a string to be used with I()
        :log_viewer_unique_name: If set, ViewLog will remember/reuse its size for this name in calibre.gui2.gprefs
        '''
        question = Question(payload, callback, cancel_callback, title, msg,
                            html_log, log_viewer_title, log_is_file, det_msg,
                            show_copy_button, checkbox_msg, checkbox_checked,
                            action_callback, action_label, action_icon,
                            focus_action, show_det, show_ok, icon,
                            log_viewer_unique_name)
        self.questions.append(question)
        self.show_question()

    def show_log(self):
        if self.questions:
            q = self.questions[0]
            log = q.html_log
            if q.log_is_file:
                with open(log, 'rb') as f:
                    log = f.read().decode('utf-8')
            self.log_viewer = ViewLog(q.log_viewer_title,
                                      log,
                                      parent=self,
                                      unique_name=q.log_viewer_unique_name)

    def paintEvent(self, ev):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
        painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True)
        try:
            if self.rendered_pixmap is None:
                self.paint_background(painter)
            else:
                self.animated_paint(painter)
        finally:
            painter.end()

    def animated_paint(self, painter):
        top = (1 - self._show_fraction) * self.height()
        painter.drawPixmap(0, top, self.rendered_pixmap)

    def paint_background(self, painter):
        br = 12  # border_radius
        bw = 1  # border_width
        pal = self.palette()
        c = pal.color(QPalette.ColorRole.Window)
        c.setAlphaF(0.9)
        p = QPainterPath()
        p.addRoundedRect(QRectF(self.rect()), br, br)
        painter.fillPath(p, c)
        p.addRoundedRect(
            QRectF(self.rect()).adjusted(bw, bw, -bw, -bw), br, br)
        painter.fillPath(p, pal.color(QPalette.ColorRole.WindowText))
コード例 #5
0
ファイル: main.py プロジェクト: zmshan2008/calibre
class XPathDialog(QDialog):  # {{{
    def __init__(self, parent, prefs):
        QDialog.__init__(self, parent)
        self.prefs = prefs
        self.setWindowTitle(_('Create ToC from XPath'))
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        self.la = la = QLabel(
            _('Specify a series of XPath expressions for the different levels of'
              ' the Table of Contents. You can use the wizard buttons to help'
              ' you create XPath expressions.'))
        la.setWordWrap(True)
        l.addWidget(la)
        self.widgets = []
        for i in range(5):
            la = _('Level %s ToC:') % ('&%d' % (i + 1))
            xp = XPathEdit(self)
            xp.set_msg(la)
            self.widgets.append(xp)
            l.addWidget(xp)

        self.bb = bb = QDialogButtonBox(
            QDialogButtonBox.StandardButton.Ok
            | QDialogButtonBox.StandardButton.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        self.ssb = b = bb.addButton(_('&Save settings'),
                                    QDialogButtonBox.ButtonRole.ActionRole)
        b.clicked.connect(self.save_settings)
        self.load_button = b = bb.addButton(
            _('&Load settings'), QDialogButtonBox.ButtonRole.ActionRole)
        self.load_menu = QMenu(b)
        b.setMenu(self.load_menu)
        self.setup_load_button()
        self.remove_duplicates_cb = QCheckBox(
            _('Do not add duplicate entries at the same level'))
        self.remove_duplicates_cb.setChecked(
            self.prefs.get('xpath_toc_remove_duplicates', True))
        l.addWidget(self.remove_duplicates_cb)
        l.addStretch()
        l.addWidget(bb)
        self.resize(self.sizeHint() + QSize(50, 75))

    def save_settings(self):
        xpaths = self.xpaths
        if not xpaths:
            return error_dialog(self,
                                _('No XPaths'),
                                _('No XPaths have been entered'),
                                show=True)
        if not self.check():
            return
        name, ok = QInputDialog.getText(self, _('Choose name'),
                                        _('Choose a name for these settings'))
        if ok:
            name = unicode_type(name).strip()
            if name:
                saved = self.prefs.get('xpath_toc_settings', {})
                # in JSON all keys have to be strings
                saved[name] = {
                    unicode_type(i): x
                    for i, x in enumerate(xpaths)
                }
                self.prefs.set('xpath_toc_settings', saved)
                self.setup_load_button()

    def setup_load_button(self):
        saved = self.prefs.get('xpath_toc_settings', {})
        m = self.load_menu
        m.clear()
        self.__actions = []
        a = self.__actions.append
        for name in sorted(saved):
            a(m.addAction(name, partial(self.load_settings, name)))
        m.addSeparator()
        a(m.addAction(_('Remove saved settings'), self.clear_settings))
        self.load_button.setEnabled(bool(saved))

    def clear_settings(self):
        self.prefs.set('xpath_toc_settings', {})
        self.setup_load_button()

    def load_settings(self, name):
        saved = self.prefs.get('xpath_toc_settings', {}).get(name, {})
        for i, w in enumerate(self.widgets):
            txt = saved.get(unicode_type(i), '')
            w.edit.setText(txt)

    def check(self):
        for w in self.widgets:
            if not w.check():
                error_dialog(self,
                             _('Invalid XPath'),
                             _('The XPath expression %s is not valid.') %
                             w.xpath,
                             show=True)
                return False
        return True

    def accept(self):
        if self.check():
            self.prefs.set('xpath_toc_remove_duplicates',
                           self.remove_duplicates_cb.isChecked())
            super(XPathDialog, self).accept()

    @property
    def xpaths(self):
        return [w.xpath for w in self.widgets if w.xpath.strip()]
コード例 #6
0
class JobError(QDialog):  # {{{

    WIDTH = 600
    do_pop = pyqtSignal()

    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
        self.queue = []
        self.do_pop.connect(self.pop, type=Qt.ConnectionType.QueuedConnection)

        self._layout = l = QGridLayout()
        self.setLayout(l)
        self.icon = QIcon(I('dialog_error.png'))
        self.setWindowIcon(self.icon)
        self.icon_widget = Icon(self)
        self.icon_widget.set_icon(self.icon)
        self.msg_label = QLabel('<p>&nbsp;')
        self.msg_label.setStyleSheet('QLabel { margin-top: 1ex; }')
        self.msg_label.setWordWrap(True)
        self.msg_label.setTextFormat(Qt.TextFormat.RichText)
        self.det_msg = QPlainTextEdit(self)
        self.det_msg.setVisible(False)

        self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close,
                                   parent=self)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.ctc_button = self.bb.addButton(
            _('&Copy to clipboard'), QDialogButtonBox.ButtonRole.ActionRole)
        self.ctc_button.clicked.connect(self.copy_to_clipboard)
        self.retry_button = self.bb.addButton(
            _('&Retry'), QDialogButtonBox.ButtonRole.ActionRole)
        self.retry_button.clicked.connect(self.retry)
        self.retry_func = None
        self.show_det_msg = _('Show &details')
        self.hide_det_msg = _('Hide &details')
        self.det_msg_toggle = self.bb.addButton(
            self.show_det_msg, QDialogButtonBox.ButtonRole.ActionRole)
        self.det_msg_toggle.clicked.connect(self.toggle_det_msg)
        self.det_msg_toggle.setToolTip(
            _('Show detailed information about this error'))
        self.suppress = QCheckBox(self)

        l.addWidget(self.icon_widget, 0, 0, 1, 1)
        l.addWidget(self.msg_label, 0, 1, 1, 1)
        l.addWidget(self.det_msg, 1, 0, 1, 2)
        l.addWidget(self.suppress, 2, 0, 1, 2,
                    Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom)
        l.addWidget(self.bb, 3, 0, 1, 2,
                    Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignBottom)
        l.setColumnStretch(1, 100)

        self.setModal(False)
        self.suppress.setVisible(False)
        self.do_resize()

    def retry(self):
        if self.retry_func is not None:
            self.accept()
            self.retry_func()

    def update_suppress_state(self):
        self.suppress.setText(
            ngettext('Hide the remaining error message',
                     'Hide the {} remaining error messages',
                     len(self.queue)).format(len(self.queue)))
        self.suppress.setVisible(len(self.queue) > 3)
        self.do_resize()

    def copy_to_clipboard(self, *args):
        d = QTextDocument()
        d.setHtml(self.msg_label.text())
        QApplication.clipboard().setText(
            'calibre, version %s (%s, embedded-python: %s)\n%s: %s\n\n%s' %
            (__version__, sys.platform, isfrozen, str(self.windowTitle()),
             str(d.toPlainText()), str(self.det_msg.toPlainText())))
        if hasattr(self, 'ctc_button'):
            self.ctc_button.setText(_('Copied'))

    def toggle_det_msg(self, *args):
        vis = str(self.det_msg_toggle.text()) == self.hide_det_msg
        self.det_msg_toggle.setText(
            self.show_det_msg if vis else self.hide_det_msg)
        self.det_msg.setVisible(not vis)
        self.do_resize()

    def do_resize(self):
        h = self.sizeHint().height()
        self.setMinimumHeight(0)  # Needed as this gets set if det_msg is shown
        # Needed otherwise re-showing the box after showing det_msg causes the box
        # to not reduce in height
        self.setMaximumHeight(h)
        self.resize(QSize(self.WIDTH, h))

    def showEvent(self, ev):
        ret = QDialog.showEvent(self, ev)
        self.bb.button(QDialogButtonBox.StandardButton.Close).setFocus(
            Qt.FocusReason.OtherFocusReason)
        return ret

    def show_error(self, title, msg, det_msg='', retry_func=None):
        self.queue.append((title, msg, det_msg, retry_func))
        self.update_suppress_state()
        self.pop()

    def pop(self):
        if not self.queue or self.isVisible():
            return
        title, msg, det_msg, retry_func = self.queue.pop(0)
        self.setWindowTitle(title)
        self.msg_label.setText(msg)
        self.det_msg.setPlainText(det_msg)
        self.det_msg.setVisible(False)
        self.det_msg_toggle.setText(self.show_det_msg)
        self.det_msg_toggle.setVisible(True)
        self.suppress.setChecked(False)
        self.update_suppress_state()
        if not det_msg:
            self.det_msg_toggle.setVisible(False)
        self.retry_button.setVisible(retry_func is not None)
        self.retry_func = retry_func
        self.do_resize()
        self.show()

    def done(self, r):
        if self.suppress.isChecked():
            self.queue = []
        QDialog.done(self, r)
        self.do_pop.emit()
コード例 #7
0
class BookInfo(QDialog):

    closed = pyqtSignal(object)
    open_cover_with = pyqtSignal(object, object)

    def __init__(self, parent, view, row, link_delegate):
        QDialog.__init__(self, parent)
        self.marked = None
        self.gui = parent
        self.splitter = QSplitter(self)
        self._l = l = QVBoxLayout(self)
        self.setLayout(l)
        l.addWidget(self.splitter)

        self.cover = Cover(self, show_size=gprefs['bd_overlay_cover_size'])
        self.cover.resizeEvent = self.cover_view_resized
        self.cover.cover_changed.connect(self.cover_changed)
        self.cover.open_with_requested.connect(self.open_with)
        self.cover.choose_open_with_requested.connect(self.choose_open_with)
        self.cover_pixmap = None
        self.cover.sizeHint = self.details_size_hint
        self.splitter.addWidget(self.cover)

        self.details = Details(parent.book_details.book_info, self)
        self.details.anchor_clicked.connect(self.on_link_clicked)
        self.link_delegate = link_delegate
        self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent,
                                  False)
        palette = self.details.palette()
        self.details.setAcceptDrops(False)
        palette.setBrush(QPalette.ColorRole.Base, Qt.GlobalColor.transparent)
        self.details.setPalette(palette)

        self.c = QWidget(self)
        self.c.l = l2 = QGridLayout(self.c)
        l2.setContentsMargins(0, 0, 0, 0)
        self.c.setLayout(l2)
        l2.addWidget(self.details, 0, 0, 1, -1)
        self.splitter.addWidget(self.c)

        self.fit_cover = QCheckBox(_('Fit &cover within view'), self)
        self.fit_cover.setChecked(
            gprefs.get('book_info_dialog_fit_cover', True))
        self.hl = hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        l2.addLayout(hl, l2.rowCount(), 0, 1, -1)
        hl.addWidget(self.fit_cover), hl.addStretch()
        self.clabel = QLabel(
            '<div style="text-align: right"><a href="calibre:conf" title="{}" style="text-decoration: none">{}</a>'
            .format(_('Configure this view'), _('Configure')))
        self.clabel.linkActivated.connect(self.configure)
        hl.addWidget(self.clabel)
        self.previous_button = QPushButton(QIcon(I('previous.png')),
                                           _('&Previous'), self)
        self.previous_button.clicked.connect(self.previous)
        l2.addWidget(self.previous_button, l2.rowCount(), 0)
        self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self)
        self.next_button.clicked.connect(self.next)
        l2.addWidget(self.next_button, l2.rowCount() - 1, 1)

        self.view = view
        self.path_to_book = None
        self.current_row = None
        self.refresh(row)
        self.view.model().new_bookdisplay_data.connect(self.slave)
        self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
        self.ns = QShortcut(QKeySequence('Alt+Right'), self)
        self.ns.activated.connect(self.next)
        self.ps = QShortcut(QKeySequence('Alt+Left'), self)
        self.ps.activated.connect(self.previous)
        self.next_button.setToolTip(
            _('Next [%s]') % unicode_type(self.ns.key().toString(
                QKeySequence.SequenceFormat.NativeText)))
        self.previous_button.setToolTip(
            _('Previous [%s]') % unicode_type(self.ps.key().toString(
                QKeySequence.SequenceFormat.NativeText)))

        geom = QCoreApplication.instance().desktop().availableGeometry(self)
        screen_height = geom.height() - 100
        screen_width = geom.width() - 100
        self.resize(max(int(screen_width / 2), 700), screen_height)
        saved_layout = gprefs.get('book_info_dialog_layout', None)
        if saved_layout is not None:
            try:
                QApplication.instance().safe_restore_geometry(
                    self, saved_layout[0])
                self.splitter.restoreState(saved_layout[1])
            except Exception:
                pass
        from calibre.gui2.ui import get_gui
        ema = get_gui().iactions['Edit Metadata'].menuless_qaction
        a = self.ema = QAction('edit metadata', self)
        a.setShortcut(ema.shortcut())
        self.addAction(a)
        a.triggered.connect(self.edit_metadata)

    def edit_metadata(self):
        if self.current_row is not None:
            book_id = self.view.model().id(self.current_row)
            get_gui().iactions['Edit Metadata'].edit_metadata_for(
                [self.current_row], [book_id], bulk=False)

    def configure(self):
        d = Configure(get_gui().current_db, self)
        if d.exec_() == QDialog.DialogCode.Accepted:
            if self.current_row is not None:
                mi = self.view.model().get_book_display_info(self.current_row)
                if mi is not None:
                    self.refresh(self.current_row, mi=mi)

    def on_link_clicked(self, qurl):
        link = unicode_type(qurl.toString(NO_URL_FORMATTING))
        self.link_delegate(link)

    def done(self, r):
        saved_layout = (bytearray(self.saveGeometry()),
                        bytearray(self.splitter.saveState()))
        gprefs.set('book_info_dialog_layout', saved_layout)
        ret = QDialog.done(self, r)
        self.view.model().new_bookdisplay_data.disconnect(self.slave)
        self.view = self.link_delegate = self.gui = None
        self.closed.emit(self)
        return ret

    def cover_changed(self, data):
        if self.current_row is not None:
            id_ = self.view.model().id(self.current_row)
            self.view.model().db.set_cover(id_, data)
        self.gui.refresh_cover_browser()
        ci = self.view.currentIndex()
        if ci.isValid():
            self.view.model().current_changed(ci, ci)

    def details_size_hint(self):
        return QSize(350, 550)

    def toggle_cover_fit(self, state):
        gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
        self.resize_cover()

    def cover_view_resized(self, event):
        QTimer.singleShot(1, self.resize_cover)

    def slave(self, mi):
        self.refresh(mi.row_number, mi)

    def move(self, delta=1):
        idx = self.view.currentIndex()
        if idx.isValid():
            m = self.view.model()
            ni = m.index(idx.row() + delta, idx.column())
            if ni.isValid():
                if self.view.isVisible():
                    self.view.scrollTo(ni)
                self.view.setCurrentIndex(ni)

    def next(self):
        self.move()

    def previous(self):
        self.move(-1)

    def resize_cover(self):
        if self.cover_pixmap is None:
            self.cover.set_marked(self.marked)
            return
        pixmap = self.cover_pixmap
        if self.fit_cover.isChecked():
            scaled, new_width, new_height = fit_image(
                pixmap.width(), pixmap.height(),
                self.cover.size().width() - 10,
                self.cover.size().height() - 10)
            if scaled:
                try:
                    dpr = self.devicePixelRatioF()
                except AttributeError:
                    dpr = self.devicePixelRatio()
                pixmap = pixmap.scaled(
                    int(dpr * new_width), int(dpr * new_height),
                    Qt.AspectRatioMode.KeepAspectRatio,
                    Qt.TransformationMode.SmoothTransformation)
                pixmap.setDevicePixelRatio(dpr)
        self.cover.set_pixmap(pixmap)
        self.cover.set_marked(self.marked)
        self.update_cover_tooltip()

    def update_cover_tooltip(self):
        tt = ''
        if self.marked:
            tt += _('This book is marked') if self.marked in {
                True, 'true'
            } else _('This book is marked as: %s') % self.marked
            tt += '\n\n'

        if self.path_to_book is not None:
            tt += textwrap.fill(_('Path: {}').format(self.path_to_book))
            tt += '\n\n'

        if self.cover_pixmap is not None:
            sz = self.cover_pixmap.size()
            tt += _('Cover size: %(width)d x %(height)d pixels') % dict(
                width=sz.width(), height=sz.height())
        self.cover.setToolTip(tt)
        self.cover.pixmap_size = sz.width(), sz.height()

    def refresh(self, row, mi=None):
        if isinstance(row, QModelIndex):
            row = row.row()
        if row == self.current_row and mi is None:
            return
        mi = self.view.model().get_book_display_info(row) if mi is None else mi
        if mi is None:
            # Indicates books was deleted from library, or row numbers have
            # changed
            return

        self.previous_button.setEnabled(False if row == 0 else True)
        self.next_button.setEnabled(False if row ==
                                    self.view.model().rowCount(QModelIndex()) -
                                    1 else True)
        self.current_row = row
        self.setWindowTitle(mi.title)
        self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1])
        self.path_to_book = getattr(mi, 'path', None)
        try:
            dpr = self.devicePixelRatioF()
        except AttributeError:
            dpr = self.devicePixelRatio()
        self.cover_pixmap.setDevicePixelRatio(dpr)
        self.marked = mi.marked
        self.resize_cover()
        html = render_html(mi,
                           True,
                           self,
                           pref_name='popup_book_display_fields')
        set_html(mi, html, self.details)
        self.update_cover_tooltip()

    def open_with(self, entry):
        id_ = self.view.model().id(self.current_row)
        self.open_cover_with.emit(id_, entry)

    def choose_open_with(self):
        from calibre.gui2.open_with import choose_program
        entry = choose_program('cover_image', self)
        if entry is not None:
            self.open_with(entry)
コード例 #8
0
class Report(QDialog):  # {{{
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.gui = parent
        self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
        self.setWindowIcon(QIcon(I('polish.png')))
        self.reports = []

        self.l = l = QGridLayout()
        self.setLayout(l)
        self.view = v = QTextEdit(self)
        v.setReadOnly(True)
        l.addWidget(self.view, 0, 0, 1, 2)

        self.backup_msg = la = QLabel('')
        l.addWidget(la, 1, 0, 1, 2)
        la.setVisible(False)
        la.setWordWrap(True)

        self.ign = QCheckBox(_('Ignore remaining reports'), self)
        l.addWidget(self.ign, 2, 0)

        bb = self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        b = self.log_button = bb.addButton(
            _('View full &log'), QDialogButtonBox.ButtonRole.ActionRole)
        b.clicked.connect(self.view_log)
        bb.button(QDialogButtonBox.StandardButton.Close).setDefault(True)
        l.addWidget(bb, 2, 1)

        self.finished.connect(self.show_next,
                              type=Qt.ConnectionType.QueuedConnection)

        self.resize(QSize(800, 600))

    def setup_ign(self):
        self.ign.setText(
            ngettext('Ignore remaining report', 'Ignore remaining {} reports',
                     len(self.reports)).format(len(self.reports)))
        self.ign.setVisible(bool(self.reports))
        self.ign.setChecked(False)

    def __call__(self, *args):
        self.reports.append(args)
        self.setup_ign()
        if not self.isVisible():
            self.show_next()

    def show_report(self, book_title, book_id, fmts, job, report):
        from calibre.ebooks.markdown import markdown
        self.current_log = job.details
        self.setWindowTitle(_('Polishing of %s') % book_title)
        self.view.setText(
            markdown('# %s\n\n' % book_title + report, output_format='html4'))
        self.bb.button(QDialogButtonBox.StandardButton.Close).setFocus(
            Qt.FocusReason.OtherFocusReason)
        self.backup_msg.setVisible(bool(fmts))
        if fmts:
            m = ngettext('The original file has been saved as %s.',
                         'The original files have been saved as %s.',
                         len(fmts)) % (_(' and ').join('ORIGINAL_' + f
                                                       for f in fmts))
            self.backup_msg.setText(m + ' ' + _(
                'If you polish again, the polishing will run on the originals.'
            ) % ())

    def view_log(self):
        self.view.setPlainText(self.current_log)
        self.view.verticalScrollBar().setValue(0)

    def show_next(self, *args):
        if not self.reports:
            return
        if not self.isVisible():
            self.show()
        self.show_report(*self.reports.pop(0))
        self.setup_ign()

    def accept(self):
        if self.ign.isChecked():
            self.reports = []
        if self.reports:
            self.show_next()
            return
        super().accept()

    def reject(self):
        if self.ign.isChecked():
            self.reports = []
        if self.reports:
            self.show_next()
            return
        super().reject()