Пример #1
0
class RemoveTags(QWidget):

    def __init__(self, parent, values):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout()
        layout.setSpacing(5)
        layout.setContentsMargins(0, 0, 0, 0)

        self.tags_box = EditWithComplete(parent)
        self.tags_box.update_items_cache(values)
        layout.addWidget(self.tags_box, stretch=3)
        self.remove_tags_button = QToolButton(parent)
        self.remove_tags_button.setToolTip(_('Open Item Editor'))
        self.remove_tags_button.setIcon(QIcon(I('chapters.png')))
        layout.addWidget(self.remove_tags_button)
        self.checkbox = QCheckBox(_('Remove all tags'), parent)
        layout.addWidget(self.checkbox)
        layout.addStretch(1)
        self.setLayout(layout)
        self.checkbox.stateChanged[int].connect(self.box_touched)

    def box_touched(self, state):
        if state:
            self.tags_box.setText('')
            self.tags_box.setEnabled(False)
        else:
            self.tags_box.setEnabled(True)
    def _init_controls(self):
        # Add the control set
        vbl = QVBoxLayout()
        self.move_element_up_tb = QToolButton()
        self.move_element_up_tb.setObjectName("move_element_up_tb")
        self.move_element_up_tb.setToolTip('Move element up')
        self.move_element_up_tb.setIcon(QIcon(I('arrow-up.png')))
        self.move_element_up_tb.clicked.connect(self.move_row_up)
        vbl.addWidget(self.move_element_up_tb)

        self.undo_css_tb = QToolButton()
        self.undo_css_tb.setObjectName("undo_css_tb")
        self.undo_css_tb.setToolTip('Restore CSS to last saved')
        self.undo_css_tb.setIcon(QIcon(I('edit-undo.png')))
        self.undo_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'undo'))
        vbl.addWidget(self.undo_css_tb)

        self.reset_css_tb = QToolButton()
        self.reset_css_tb.setObjectName("reset_css_tb")
        self.reset_css_tb.setToolTip('Reset CSS to default')
        self.reset_css_tb.setIcon(QIcon(I('trash.png')))
        self.reset_css_tb.clicked.connect(partial(self.undo_reset_button_clicked, 'reset'))
        vbl.addWidget(self.reset_css_tb)

        self.move_element_down_tb = QToolButton()
        self.move_element_down_tb.setObjectName("move_element_down_tb")
        self.move_element_down_tb.setToolTip('Move element down')
        self.move_element_down_tb.setIcon(QIcon(I('arrow-down.png')))
        self.move_element_down_tb.clicked.connect(self.move_row_down)
        vbl.addWidget(self.move_element_down_tb)

        self.layout.addLayout(vbl)
Пример #3
0
    def __init__(self, parent, db, author, series=None):
        QDialog.__init__(self, parent)
        self.db = db

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

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

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

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

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

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

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

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

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

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

        self.create_epub = c = QCheckBox(_('Create an empty EPUB file as well'))
        c.setChecked(gprefs.get('create_empty_epub_file', False))
        c.setToolTip(_('Also create an empty EPUB file that you can subsequently edit'))
        self._layout.addWidget(c, 6, 0, 1, -1)

        button_box = self.bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self._layout.addWidget(button_box, 7, 0, 1, -1)
        self.resize(self.sizeHint())
Пример #4
0
 def __init__(self, color, parent=None):
     QToolButton.__init__(self, parent)
     self.setIconSize(QSize(50, 25))
     self.pix = QPixmap(self.iconSize())
     self._color = QColor('#' + color)
     self.pix.fill(self._color)
     self.setIcon(QIcon(self.pix))
     self.clicked.connect(self.choose_color)
Пример #5
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     self.animation = QPropertyAnimation(self, 'iconSize', self)
     self.animation.setDuration(60/72.*1000)
     self.animation.setLoopCount(4)
     self.normal_icon_size = QSize(64, 64)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
Пример #6
0
class MovedDialog(QDialog):  # {{{
    def __init__(self, stats, location, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_("No library found"))
        self._l = l = QGridLayout(self)
        self.setLayout(l)
        self.stats, self.location = stats, location

        loc = self.oldloc = location.replace("/", os.sep)
        self.header = QLabel(
            _(
                "No existing calibre library was found at %s. "
                "If the library was moved, select its new location below. "
                "Otherwise calibre will forget this library."
            )
            % loc
        )
        self.header.setWordWrap(True)
        ncols = 2
        l.addWidget(self.header, 0, 0, 1, ncols)
        self.cl = QLabel("<br><b>" + _("New location of this library:"))
        l.addWidget(self.cl, 1, 0, 1, ncols)
        self.loc = QLineEdit(loc, self)
        l.addWidget(self.loc, 2, 0, 1, 1)
        self.cd = QToolButton(self)
        self.cd.setIcon(QIcon(I("document_open.png")))
        self.cd.clicked.connect(self.choose_dir)
        l.addWidget(self.cd, 2, 1, 1, 1)
        self.bb = QDialogButtonBox(QDialogButtonBox.Abort)
        b = self.bb.addButton(_("Library moved"), self.bb.AcceptRole)
        b.setIcon(QIcon(I("ok.png")))
        b = self.bb.addButton(_("Forget library"), self.bb.RejectRole)
        b.setIcon(QIcon(I("edit-clear.png")))
        b.clicked.connect(self.forget_library)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb, 3, 0, 1, ncols)
        self.resize(self.sizeHint() + QSize(100, 50))

    def choose_dir(self):
        d = choose_dir(self, "library moved choose new loc", _("New library location"), default_dir=self.oldloc)
        if d is not None:
            self.loc.setText(d)

    def forget_library(self):
        self.stats.remove(self.location)

    def accept(self):
        newloc = unicode(self.loc.text())
        if not db_class().exists_at(newloc):
            error_dialog(self, _("No library found"), _("No existing calibre library found at %s") % newloc, show=True)
            return
        self.stats.rename(self.location, newloc)
        self.newloc = newloc
        QDialog.accept(self)
Пример #7
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     self._icon_size = -1
     QToolButton.setIcon(self, QIcon(I('donate.png')))
     self.setText('\xa0')
     self.animation = QPropertyAnimation(self, b'icon_size', self)
     self.animation.setDuration(60/72.*1000)
     self.animation.setLoopCount(4)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
Пример #8
0
    def __init__(self, current_family, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_('Choose font family'))
        self.setWindowIcon(QIcon(I('font.png')))
        from calibre.utils.fonts.scanner import font_scanner
        self.font_scanner = font_scanner

        self.m = QStringListModel(self)
        self.build_font_list()
        self.l = l = QGridLayout()
        self.setLayout(l)
        self.view = FontsView(self)
        self.view.setModel(self.m)
        self.view.setCurrentIndex(self.m.index(0))
        if current_family:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(current_family):
                    self.view.setCurrentIndex(self.m.index(i))
                    break
        self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection)
        self.view.changed.connect(self.current_changed,
                type=Qt.QueuedConnection)
        self.faces = Typefaces(self)
        self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.add_fonts_button = afb = self.bb.addButton(_('Add &fonts'),
                self.bb.ActionRole)
        afb.setIcon(QIcon(I('plus.png')))
        afb.clicked.connect(self.add_fonts)
        self.ml = QLabel(_('Choose a font family from the list below:'))
        self.search = QLineEdit(self)
        self.search.setPlaceholderText(_('Search'))
        self.search.returnPressed.connect(self.find)
        self.nb = QToolButton(self)
        self.nb.setIcon(QIcon(I('arrow-down.png')))
        self.nb.setToolTip(_('Find next'))
        self.pb = QToolButton(self)
        self.pb.setIcon(QIcon(I('arrow-up.png')))
        self.pb.setToolTip(_('Find previous'))
        self.nb.clicked.connect(self.find_next)
        self.pb.clicked.connect(self.find_previous)

        l.addWidget(self.ml, 0, 0, 1, 4)
        l.addWidget(self.search, 1, 0, 1, 1)
        l.addWidget(self.nb, 1, 1, 1, 1)
        l.addWidget(self.pb, 1, 2, 1, 1)
        l.addWidget(self.view, 2, 0, 1, 3)
        l.addWidget(self.faces, 1, 3, 2, 1)
        l.addWidget(self.bb, 3, 0, 1, 4)
        l.setAlignment(self.faces, Qt.AlignTop)

        self.resize(800, 600)
Пример #9
0
    def __init__(self, icon, text, splitter=None, parent=None, shortcut=None):
        QToolButton.__init__(self, parent)
        self.label = text
        self.setIcon(QIcon(icon))
        self.setCheckable(True)
        self.icname = os.path.basename(icon).rpartition('.')[0]

        self.splitter = splitter
        if splitter is not None:
            splitter.state_changed.connect(self.update_state)
        self.setCursor(Qt.PointingHandCursor)
        self.shortcut = shortcut or ''
Пример #10
0
    def __init__(self, icon, text, splitter=None, parent=None, shortcut=None):
        QToolButton.__init__(self, parent)
        self.label = text
        self.setIcon(QIcon(icon))
        self.setCheckable(True)

        self.splitter = splitter
        if splitter is not None:
            splitter.state_changed.connect(self.update_state)
        self.setCursor(Qt.PointingHandCursor)
        self.shortcut = ''
        if shortcut:
            self.shortcut = shortcut
Пример #11
0
 def __init__(self, *args):
     QToolButton.__init__(self, *args)
     # vertically size policy must be expanding for it to align inside a
     # toolbar
     self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
     self._icon_size = -1
     QToolButton.setIcon(self, QIcon(I('donate.png')))
     self.setText('\xa0')
     self.animation = QPropertyAnimation(self, b'icon_size', self)
     self.animation.setDuration(60/72.*1000)
     self.animation.setLoopCount(4)
     self.animation.valueChanged.connect(self.value_changed)
     self.setCursor(Qt.PointingHandCursor)
     self.animation.finished.connect(self.animation_finished)
Пример #12
0
class MultipleWidget(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout()
        layout.setSpacing(5)
        layout.setContentsMargins(0, 0, 0, 0)

        self.tags_box = EditWithComplete(parent)
        layout.addWidget(self.tags_box, stretch=1000)
        self.editor_button = QToolButton(self)
        self.editor_button.setToolTip(_('Open Item Editor'))
        self.editor_button.setIcon(QIcon(I('chapters.png')))
        layout.addWidget(self.editor_button)
        self.setLayout(layout)

    def get_editor_button(self):
        return self.editor_button

    def update_items_cache(self, values):
        self.tags_box.update_items_cache(values)

    def clear(self):
        self.tags_box.clear()

    def setEditText(self):
        self.tags_box.setEditText()

    def addItem(self, itm):
        self.tags_box.addItem(itm)

    def set_separator(self, sep):
        self.tags_box.set_separator(sep)

    def set_add_separator(self, sep):
        self.tags_box.set_add_separator(sep)

    def set_space_before_sep(self, v):
        self.tags_box.set_space_before_sep(v)

    def setSizePolicy(self, v1, v2):
        self.tags_box.setSizePolicy(v1, v2)

    def setText(self, v):
        self.tags_box.setText(v)

    def text(self):
        return self.tags_box.text()
Пример #13
0
    def __init__(self, stats, location, parent=None):
        QDialog.__init__(self, parent)
        self.setWindowTitle(_('No library found'))
        self._l = l = QGridLayout(self)
        self.setLayout(l)
        self.stats, self.location = stats, location

        loc = self.oldloc = location.replace('/', os.sep)
        self.header = QLabel(_('No existing calibre library was found at %s. '
            'If the library was moved, select its new location below. '
            'Otherwise calibre will forget this library.')%loc)
        self.header.setWordWrap(True)
        ncols = 2
        l.addWidget(self.header, 0, 0, 1, ncols)
        self.cl = QLabel('<br><b>'+_('New location of this library:'))
        l.addWidget(self.cl, 1, 0, 1, ncols)
        self.loc = QLineEdit(loc, self)
        l.addWidget(self.loc, 2, 0, 1, 1)
        self.cd = QToolButton(self)
        self.cd.setIcon(QIcon(I('document_open.png')))
        self.cd.clicked.connect(self.choose_dir)
        l.addWidget(self.cd, 2, 1, 1, 1)
        self.bb = QDialogButtonBox(QDialogButtonBox.Abort)
        b = self.bb.addButton(_('Library moved'), self.bb.AcceptRole)
        b.setIcon(QIcon(I('ok.png')))
        b = self.bb.addButton(_('Forget library'), self.bb.RejectRole)
        b.setIcon(QIcon(I('edit-clear.png')))
        b.clicked.connect(self.forget_library)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        l.addWidget(self.bb, 3, 0, 1, ncols)
        self.resize(self.sizeHint() + QSize(100, 50))
Пример #14
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.Fixed, QSizePolicy.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

    @dynamic_property
    def font_family(self):
        def fget(self):
            return self._current_family

        def fset(self, val):
            if not val:
                val = None
            self._current_family = val
            self.button.setText(val or self.default_text)
            self.family_changed.emit(val)
        return property(fget=fget, fset=fset)

    def show_chooser(self):
        d = FontFamilyDialog(self.font_family, self)
        if d.exec_() == d.Accepted:
            self.font_family = d.font_family
Пример #15
0
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAutoFillBackground(True)
        self.capture = 0

        self.setFrameShape(self.StyledPanel)
        self.setFrameShadow(self.Raised)
        self._layout = l = QGridLayout(self)
        self.setLayout(l)

        self.header = QLabel('')
        l.addWidget(self.header, 0, 0, 1, 2)

        self.use_default = QRadioButton('')
        self.use_custom = QRadioButton(_('Custom'))
        l.addWidget(self.use_default, 1, 0, 1, 3)
        l.addWidget(self.use_custom, 2, 0, 1, 3)
        self.use_custom.toggled.connect(self.custom_toggled)

        off = 2
        for which in (1, 2):
            text = _('&Shortcut:') if which == 1 else _('&Alternate shortcut:')
            la = QLabel(text)
            la.setStyleSheet('QLabel { margin-left: 1.5em }')
            l.addWidget(la, off+which, 0, 1, 3)
            setattr(self, 'label%d'%which, la)
            button = QPushButton(_('None'), self)
            button.clicked.connect(partial(self.capture_clicked, which=which))
            button.keyPressEvent = partial(self.key_press_event, which=which)
            setattr(self, 'button%d'%which, button)
            clear = QToolButton(self)
            clear.setIcon(QIcon(I('clear_left.png')))
            clear.clicked.connect(partial(self.clear_clicked, which=which))
            setattr(self, 'clear%d'%which, clear)
            l.addWidget(button, off+which, 1, 1, 1)
            l.addWidget(clear, off+which, 2, 1, 1)
            la.setBuddy(button)

        self.done_button = doneb = QPushButton(_('Done'), self)
        l.addWidget(doneb, 0, 2, 1, 1)
        doneb.clicked.connect(lambda : self.editing_done.emit(self))
        l.setColumnStretch(0, 100)

        self.custom_toggled(False)
Пример #16
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.use_stemmer = parent.use_stemmer
        self.current_query = None
        l = QVBoxLayout(self)

        h = QHBoxLayout()
        l.addLayout(h)
        self.search_box = sb = SearchBox(self)
        sb.initialize('library-annotations-browser-search-box')
        sb.cleared.connect(self.cleared,
                           type=Qt.ConnectionType.QueuedConnection)
        sb.lineEdit().returnPressed.connect(self.show_next)
        sb.lineEdit().setPlaceholderText(_('Enter words to search for'))
        h.addWidget(sb)

        self.next_button = nb = QToolButton(self)
        h.addWidget(nb)
        nb.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        nb.setIcon(QIcon(I('arrow-down.png')))
        nb.clicked.connect(self.show_next)
        nb.setToolTip(_('Find next match'))

        self.prev_button = nb = QToolButton(self)
        h.addWidget(nb)
        nb.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        nb.setIcon(QIcon(I('arrow-up.png')))
        nb.clicked.connect(self.show_previous)
        nb.setToolTip(_('Find previous match'))

        self.restrictions = rs = Restrictions(self)
        rs.restrictions_changed.connect(self.effective_query_changed)
        self.use_stemmer.stateChanged.connect(self.effective_query_changed)
        l.addWidget(rs)

        self.results_list = rl = ResultsList(self)
        rl.current_result_changed.connect(self.current_result_changed)
        rl.open_annotation.connect(self.open_annotation)
        rl.show_book.connect(self.show_book)
        rl.edit_annotation.connect(self.edit_annotation)
        rl.delete_requested.connect(self.delete_requested)
        rl.export_requested.connect(self.export_requested)
        l.addWidget(rl)
Пример #17
0
    def setup_ui(self):
        from calibre.gui2.preferences.look_feel import DisplayedFields, move_field_up, move_field_down
        self.l = QVBoxLayout(self)
        self.field_display_order = fdo = QListView(self)
        self.model = DisplayedFields(self.db,
                                     fdo,
                                     pref_name='popup_book_display_fields')
        self.model.initialize()
        fdo.setModel(self.model)
        fdo.setAlternatingRowColors(True)
        del self.db
        self.l.addWidget(QLabel(_('Select displayed metadata')))
        h = QHBoxLayout()
        h.addWidget(fdo)
        v = QVBoxLayout()
        self.mub = b = QToolButton(self)
        connect_lambda(b.clicked, self,
                       lambda self: move_field_up(fdo, self.model))
        b.setIcon(QIcon(I('arrow-up.png')))
        b.setToolTip(_('Move the selected field up'))
        v.addWidget(b), v.addStretch(10)
        self.mud = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-down.png')))
        b.setToolTip(_('Move the selected field down'))
        connect_lambda(b.clicked, self,
                       lambda self: move_field_down(fdo, self.model))
        v.addWidget(b)
        h.addLayout(v)

        self.l.addLayout(h)
        self.l.addWidget(
            QLabel('<p>' + _(
                'Note that <b>comments</b> will always be displayed at the end, regardless of the order you assign here'
            )))

        b = self.bb.addButton(_('Restore &defaults'), self.bb.ActionRole)
        b.clicked.connect(self.restore_defaults)
        b = self.bb.addButton(_('Select &all'), self.bb.ActionRole)
        b.clicked.connect(self.select_all)
        b = self.bb.addButton(_('Select &none'), self.bb.ActionRole)
        b.clicked.connect(self.select_none)
        self.l.addWidget(self.bb)
        self.setMinimumHeight(500)
Пример #18
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)

        self.msg_label = la = QLabel(
            '<p>' + _('You can specify rules to filter/transform tags here. Click the "Add Rule" button'
            ' below to get started. The rules will be processed in order for every tag until either a'
            ' "remove" or a "keep" rule matches.') + '<p>' + _(
            'You can <b>change an existing rule</b> by double clicking it')
        )
        la.setWordWrap(True)
        l.addWidget(la)
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.add_button = b = QPushButton(QIcon(I('plus.png')), _('&Add rule'), self)
        b.clicked.connect(self.add_rule)
        h.addWidget(b)
        self.remove_button = b = QPushButton(QIcon(I('minus.png')), _('&Remove rule(s)'), self)
        b.clicked.connect(self.remove_rules)
        h.addWidget(b)
        self.h3 = h = QHBoxLayout()
        l.addLayout(h)
        self.rule_list = r = QListWidget(self)
        self.delegate = Delegate(self)
        r.setSelectionMode(r.ExtendedSelection)
        r.setItemDelegate(self.delegate)
        r.doubleClicked.connect(self.edit_rule)
        h.addWidget(r)
        r.setDragEnabled(True)
        r.viewport().setAcceptDrops(True)
        r.setDropIndicatorShown(True)
        r.setDragDropMode(r.InternalMove)
        r.setDefaultDropAction(Qt.MoveAction)
        self.l2 = l = QVBoxLayout()
        h.addLayout(l)
        self.up_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-up.png'))), b.setToolTip(_('Move current rule up'))
        b.clicked.connect(self.move_up)
        l.addWidget(b)
        self.down_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-down.png'))), b.setToolTip(_('Move current rule down'))
        b.clicked.connect(self.move_down)
        l.addStretch(10), l.addWidget(b)
Пример #19
0
    def setup_ui(self):
        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.bb.setStandardButtons(self.bb.Close)
        self.rearrange_button = b = self.bb.addButton(
            _('Re-arrange favorites'), self.bb.ActionRole)
        b.setCheckable(True)
        b.setChecked(False)
        b.setVisible(False)
        b.setDefault(True)

        self.splitter = s = QSplitter(self)
        s.setChildrenCollapsible(False)

        self.search = h = HistoryLineEdit2(self)
        h.setToolTip(
            textwrap.fill(
                _('Search for unicode characters by using the English names or nicknames.'
                  ' You can also search directly using a character code. For example, the following'
                  ' searches will all yield the no-break space character: U+A0, nbsp, no-break'
                  )))
        h.initialize('charmap_search')
        h.setPlaceholderText(_('Search by name, nickname or character code'))
        self.search_button = b = QPushButton(_('&Search'))
        h.returnPressed.connect(self.do_search)
        b.clicked.connect(self.do_search)
        self.clear_button = cb = QToolButton(self)
        cb.setIcon(QIcon(I('clear_left.png')))
        cb.setText(_('Clear search'))
        cb.clicked.connect(self.clear_search)
        l.addWidget(h), l.addWidget(b, 0, 1), l.addWidget(cb, 0, 2)

        self.category_view = CategoryView(self)
        l.addWidget(s, 1, 0, 1, 3)
        self.char_view = CharView(self)
        self.rearrange_button.toggled[bool].connect(
            self.set_allow_drag_and_drop)
        self.category_view.category_selected.connect(self.show_chars)
        self.char_view.show_name.connect(self.show_char_info)
        self.char_view.char_selected.connect(self.char_selected)
        s.addWidget(self.category_view), s.addWidget(self.char_view)

        self.char_info = la = QLabel('\xa0')
        la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        l.addWidget(la, 2, 0, 1, 3)

        self.rearrange_msg = la = QLabel(
            _('Drag and drop characters to re-arrange them. Click the re-arrange button again when you are done.'
              ))
        la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        la.setVisible(False)
        l.addWidget(la, 3, 0, 1, 3)
        l.addWidget(self.bb, 4, 0, 1, 3)
        self.char_view.setFocus(Qt.OtherFocusReason)
Пример #20
0
    def __init__(self, parent=None):
        QFrame.__init__(self, parent)
        self.setFrameShape(self.StyledPanel)
        self.search_index = -1
        self.search = {}
        self.original_name = None

        self.l = l = QVBoxLayout(self)
        self.title = la = QLabel('<h2>Edit...')
        self.ht = h = QHBoxLayout()
        l.addLayout(h)
        h.addWidget(la)
        self.cb = cb = QToolButton(self)
        cb.setIcon(QIcon(I('window-close.png')))
        cb.setToolTip(_('Abort editing of search'))
        h.addWidget(cb)
        cb.clicked.connect(self.abort_editing)
        self.search_name = n = QLineEdit('', self)
        n.setPlaceholderText(_('The name with which to save this search'))
        self.la1 = la = QLabel(_('&Name:'))
        la.setBuddy(n)
        self.h3 = h = QHBoxLayout()
        h.addWidget(la), h.addWidget(n)
        l.addLayout(h)

        self.find = f = QPlainTextEdit('', self)
        self.la2 = la = QLabel(_('&Find:'))
        la.setBuddy(f)
        l.addWidget(la), l.addWidget(f)

        self.replace = r = QPlainTextEdit('', self)
        self.la3 = la = QLabel(_('&Replace:'))
        la.setBuddy(r)
        l.addWidget(la), l.addWidget(r)

        self.case_sensitive = c = QCheckBox(_('Case sensitive'))
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        h.addWidget(c)

        self.dot_all = d = QCheckBox(_('Dot matches all'))
        h.addWidget(d), h.addStretch(2)

        self.h2 = h = QHBoxLayout()
        l.addLayout(h)
        self.mode_box = m = ModeBox(self)
        m.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.la4 = la = QLabel(_('&Mode:'))
        la.setBuddy(m)
        h.addWidget(la), h.addWidget(m), h.addStretch(2)

        self.done_button = b = QPushButton(QIcon(I('ok.png')), _('&Done'))
        b.setToolTip(_('Finish editing of search'))
        h.addWidget(b)
        b.clicked.connect(self.emit_done)
Пример #21
0
    def setup_ui(self):
        acnames = all_actions().all_action_names
        self.available_actions = ActionsList(acnames -
                                             frozenset(current_actions()),
                                             parent=self)
        self.current_actions = ActionsList(current_actions(),
                                           parent=self,
                                           is_source=False)
        self.l = l = QVBoxLayout(self)
        self.la = la = QLabel(
            _('Choose the actions you want on the toolbar.'
              ' Drag and drop items in the right hand list to re-arrange the toolbar.'
              ))
        la.setWordWrap(True)
        l.addWidget(la)
        self.bv = bv = QVBoxLayout()
        bv.addStretch(10)
        self.add_button = b = QToolButton(self)
        b.setIcon(QIcon(I('forward.png'))), b.setToolTip(
            _('Add selected actions to the toolbar'))
        bv.addWidget(b), bv.addStretch(10)
        b.clicked.connect(self.add_actions)
        self.remove_button = b = QToolButton(self)
        b.setIcon(QIcon(I('back.png'))), b.setToolTip(
            _('Remove selected actions from the toolbar'))
        b.clicked.connect(self.remove_actions)
        bv.addWidget(b), bv.addStretch(10)

        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.lg = lg = QGroupBox(_('A&vailable actions'), self)
        lg.v = v = QVBoxLayout(lg)
        v.addWidget(self.available_actions)
        h.addWidget(lg)
        self.rg = rg = QGroupBox(_('&Current actions'), self)
        rg.v = v = QVBoxLayout(rg)
        v.addWidget(self.current_actions)
        h.addLayout(bv), h.addWidget(rg)
        l.addWidget(self.bb)
        self.rdb = b = self.bb.addButton(_('Restore defaults'),
                                         self.bb.ActionRole)
        b.clicked.connect(self.restore_defaults)
Пример #22
0
    def setup_ui(self):
        self.vl = vl = QVBoxLayout(self)
        self.l = l = QFormLayout()
        vl.addLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        l.addRow(
            QLabel(_('Print %s to a PDF file') % elided_text(self.book_title)))
        self.h = h = QHBoxLayout()
        self.file_name = f = QLineEdit(self)
        val = dynamic.get(self.OUTPUT_NAME, None)
        if not val:
            val = expanduser('~')
        else:
            val = os.path.dirname(val)
        f.setText(os.path.abspath(os.path.join(val, self.default_file_name)))
        self.browse_button = b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png'))), b.setToolTip(
            _('Choose location for PDF file'))
        b.clicked.connect(self.choose_file)
        h.addWidget(f), h.addWidget(b)
        f.setMinimumWidth(350)
        w = QLabel(_('&File:'))
        l.addRow(w, h), w.setBuddy(f)

        self.paper_size = ps = PaperSizes(self)
        ps.initialize()
        ps.set_value_for_config = vprefs.get('print-to-pdf-page-size', None)
        l.addRow(_('Paper &size:'), ps)
        tmap = {
            'left': _('&Left margin:'),
            'top': _('&Top margin:'),
            'right': _('&Right margin:'),
            'bottom': _('&Bottom margin:'),
        }
        for edge in 'left top right bottom'.split():
            m = QDoubleSpinBox(self)
            m.setSuffix(' ' + _('inches'))
            m.setMinimum(0), m.setMaximum(3), m.setSingleStep(0.1)
            val = vprefs.get('print-to-pdf-%s-margin' % edge, 1)
            m.setValue(val)
            setattr(self, '%s_margin' % edge, m)
            l.addRow(tmap[edge], m)
        self.pnum = pnum = QCheckBox(_('Add page &number to printed pages'),
                                     self)
        pnum.setChecked(vprefs.get('print-to-pdf-page-numbers', True))
        l.addRow(pnum)

        self.show_file = sf = QCheckBox(_('&Open PDF file after printing'),
                                        self)
        sf.setChecked(vprefs.get('print-to-pdf-show-file', True))
        l.addRow(sf)

        vl.addStretch(10)
        vl.addWidget(self.bb)
Пример #23
0
    def __init__(self, parent=None):
        QDialog.__init__(self, parent=parent)
        self.l = l = QFormLayout(self)
        self.setLayout(l)
        self.setWindowTitle(_('Import OPML file'))
        self.setWindowIcon(QIcon(I('opml.png')))

        self.h = h = QHBoxLayout()
        self.path = p = QLineEdit(self)
        p.setMinimumWidth(300)
        p.setPlaceholderText(_('Path to OPML file'))
        h.addWidget(p)
        self.cfb = b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png')))
        b.setToolTip(_('Browse for OPML file'))
        b.clicked.connect(self.choose_file)
        h.addWidget(b)
        l.addRow(_('&OPML file:'), h)
        l.labelForField(h).setBuddy(p)
        b.setFocus(Qt.OtherFocusReason)

        self._articles_per_feed = a = QSpinBox(self)
        a.setMinimum(1), a.setMaximum(1000), a.setValue(100)
        a.setToolTip(_('Maximum number of articles to download per RSS feed'))
        l.addRow(_('&Maximum articles per feed:'), a)

        self._oldest_article = o = QSpinBox(self)
        o.setMinimum(1), o.setMaximum(3650), o.setValue(7)
        o.setSuffix(_(' days'))
        o.setToolTip(
            _('Articles in the RSS feeds older than this will be ignored'))
        l.addRow(_('&Oldest article:'), o)

        self.preserve_groups = g = QCheckBox(
            _('Preserve groups in the OPML file'))
        g.setToolTip('<p>' + _(
            'If enabled, every group of feeds in the OPML file will be converted into a single recipe. Otherwise every feed becomes its own recipe'
        ))
        g.setChecked(True)
        l.addRow(g)

        self._replace_existing = r = QCheckBox(_('Replace existing recipes'))
        r.setToolTip('<p>' + _(
            'If enabled, any existing recipes with the same titles as entries in the OPML file will be replaced.'
            ' Otherwise, new entries with modified titles will be created'))
        r.setChecked(True)
        l.addRow(r)

        self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok
                                        | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
        l.addRow(bb)

        self.recipes = ()
Пример #24
0
            def contextMenuEvent(self, event):
                menu = QMenu(self)
                attachAction = menu.addAction("Attach")
                attachAction.setProperty("sub_window_object_name", self._prop)
                attachAction.triggered.connect(self.parent().onAttachClicked)
                detachAction = menu.addAction("Detach")
                detachAction.setProperty("sub_window_object_name", self._prop)
                detachAction.triggered.connect(self.parent().onDetachClicked)
                menu.exec_(self.mapToGlobal(event.pos()))

                return QToolButton.contextMenuEvent(self, event)
Пример #25
0
 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.Fixed, QSizePolicy.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)
Пример #26
0
    def __init__(self, color_caption):
        TestableWidget.__init__(self)
        self.setWindowFlags(Qt.Popup)
        self._text = self.tr("Alignment text")

        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)

        b = "border: 1px solid #9b9b9b;"
        self._styles = {
            "border": b,
            "border_blue": "border: 0px solid blue;",
            "frame": 'QFrame[frameShape="4"]{color: #9b9b9b;}'
        }

        self.setStyleSheet(self._styles["frame"])

        # -- CAPTION ----------------------------------
        caption = QFrame(self, flags=Qt.WindowFlags())
        caption_layout = QHBoxLayout()
        self._lbl_caption = QLabel(self._text)
        self._lbl_caption.setStyleSheet(self._styles["border_blue"])
        caption_layout.addWidget(self._lbl_caption, alignment=Qt.Alignment())
        caption.setLayout(caption_layout)
        caption_layout.setContentsMargins(9, 5, 9, 5)
        caption.setStyleSheet(
            f"background-color: {color_caption}; {self._styles['border']}")

        # -- CELLS GRID -------------------------------
        cellsbox = QFrame(self, flags=Qt.WindowFlags())
        cells = QGridLayout()
        cellsbox.setLayout(cells)
        cells.setContentsMargins(9, 0, 9, 9)
        self._clrbtn = []
        for i in range(1, 4):
            for j in range(1, 5):
                self._clrbtn.append(QToolButton())
                self._clrbtn[-1].clicked.connect(
                    lambda z, y=i, x=j: self.select_align_(x, y))
                self._clrbtn[-1].setAutoRaise(True)

                sz = 48
                self._clrbtn[-1].setFixedSize(sz, sz)
                self._clrbtn[-1].setIconSize(QSize(sz, sz))

                self._clrbtn[-1].setIcon(QIcon(img(f"editor/a{i}{j}.png")))
                # noinspection PyArgumentList
                cells.addWidget(self._clrbtn[-1], i - 1, j - 1)

        # ---------------------------------------------
        main_layout.addWidget(caption, alignment=Qt.Alignment())
        main_layout.addWidget(cellsbox, alignment=Qt.Alignment())

        self.setLayout(main_layout)
Пример #27
0
 def setupStatusbar(self):
     self.alarmStatusWidget = QToolButton()
     icon = QtGui.QIcon()
     icon.addPixmap(QtGui.QPixmap("images/alarm-clock.png"), QtGui.QIcon.Normal, QtGui.QIcon.On)
     self.alarmStatusWidget.setIcon(icon)
     self.alarmStatusWidget.setFixedSize(QSize(24,24))
     self.alarmStatusWidget.setIconSize(QSize(24,24))
     
     self.statusBar.setStyleSheet("""QStatusBar::item {
                                     border: none;
                                     }""")
Пример #28
0
 def mouseReleaseEvent(self, ev):
     if ev.button() == Qt.RightButton:
         tab_name = {'book':'book_details', 'grid':'cover_grid', 'cover_flow':'cover_browser', 'tags':'tag_browser'}.get(self.icname)
         if tab_name:
             from calibre.gui2.ui import get_gui
             gui = get_gui()
             if gui is not None:
                 gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Look & Feel', tab_name+'_tab'), close_after_initial=True)
                 ev.accept()
                 return
     return QToolButton.mouseReleaseEvent(self, ev)
Пример #29
0
    def __init__(self, text=None, color_caption="#F0F0FF", icon=""):
        """
        text - text in caption and tooltip
        icon - filename for icon of button
        """
        QToolButton.__init__(self, None)
        self.setAutoRaise(True)

        self._text = self.tr("Alignment") if text is None else text
        self._color_caption = color_caption

        # ------------------------------------------------------
        self._colors_widget = AlignTextWidget(color_caption=color_caption)
        self._colors_widget.select_align.connect(self.select_align_)
        self._colors_widget.hide_form.connect(self.hide_form)

        self._action = QAction(self._text, None)
        self._action.setIcon(QIcon(img(icon)))
        self._action.triggered.connect(self.show_form)
        self.setDefaultAction(self._action)
Пример #30
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)

        self.msg_label = la = QLabel(
            '<p>' + self.MSG + '<p>' + _(
            'You can <b>change an existing rule</b> by double clicking it')
        )
        la.setWordWrap(True)
        l.addWidget(la)
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.add_button = b = QPushButton(QIcon(I('plus.png')), _('&Add rule'), self)
        b.clicked.connect(self.add_rule)
        h.addWidget(b)
        self.remove_button = b = QPushButton(QIcon(I('minus.png')), _('&Remove rule(s)'), self)
        b.clicked.connect(self.remove_rules)
        h.addWidget(b)
        self.h3 = h = QHBoxLayout()
        l.addLayout(h)
        self.rule_list = r = QListWidget(self)
        self.delegate = Delegate(self)
        r.setSelectionMode(r.ExtendedSelection)
        r.setItemDelegate(self.delegate)
        r.doubleClicked.connect(self.edit_rule)
        h.addWidget(r)
        r.setDragEnabled(True)
        r.viewport().setAcceptDrops(True)
        r.setDropIndicatorShown(True)
        r.setDragDropMode(r.InternalMove)
        r.setDefaultDropAction(Qt.MoveAction)
        self.l2 = l = QVBoxLayout()
        h.addLayout(l)
        self.up_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-up.png'))), b.setToolTip(_('Move current rule up'))
        b.clicked.connect(self.move_up)
        l.addWidget(b)
        self.down_button = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-down.png'))), b.setToolTip(_('Move current rule down'))
        b.clicked.connect(self.move_down)
        l.addStretch(10), l.addWidget(b)
Пример #31
0
    def _initialize_layout(self):
        # Get the current database
        database = self.gui.current_db
        library_config = get_library_config(database)
        columns_from_preferences = library_config[PREFS_KEY_COLUMNS]

        # remove already configured columns from available columns
        for key in columns_from_preferences.keys():
            if key in self.available_columns:
                del self.available_columns[key]

        # Build the gui
        layoutH = QHBoxLayout()

        self.sourceList = CustomListWidget(self.gui, self.available_columns)
        self.sourceList.setToolTip(_('List of available columns'))
        layoutH.addWidget(self.sourceList)

        button_layout = QVBoxLayout()
        layoutH.addLayout(button_layout)

        self.use_btn = QToolButton(self.gui)
        self.use_btn.setIcon(QIcon(I('forward.png')))
        self.use_btn.setToolTip(_('Use the column for statistic'))
        self.use_btn.clicked.connect(self._add_row)

        self.no_use_btn = QToolButton(self.gui)
        self.no_use_btn.setIcon(QIcon(I('back.png')))
        self.no_use_btn.setToolTip(_('Don\'t use the column for statistics'))
        self.no_use_btn.clicked.connect(self._remove_row)

        button_layout.addWidget(self.use_btn)
        button_layout.addStretch(1)
        button_layout.addWidget(self.no_use_btn)

        self.destinationList = CustomListWidget(self.gui,
                                                columns_from_preferences)
        self.destinationList.setToolTip(_('List of evaluated columns'))
        layoutH.addWidget(self.destinationList)

        self.setLayout(layoutH)
Пример #32
0
 def __init__(self, parent=None):
     QWidget.__init__(self, parent)
     h = QHBoxLayout(self)
     h.setContentsMargins(0, 0, 0, 0)
     self.browser = nd = Details(self)
     h.addWidget(nd)
     self.current_notes = ''
     self.edit_button = eb = QToolButton(self)
     eb.setIcon(QIcon(I('modified.png')))
     eb.setToolTip(_('Edit the notes for this highlight'))
     h.addWidget(eb)
     eb.clicked.connect(self.edit_notes)
Пример #33
0
 def __init__(self, index, dup_check, parent=None):
     QFrame.__init__(self, parent)
     self.setFrameShape(self.StyledPanel)
     self.setFrameShadow(self.Raised)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setAutoFillBackground(True)
     self.l = l = QVBoxLayout(self)
     self.header = la = QLabel(self)
     la.setWordWrap(True)
     l.addWidget(la)
     self.default_shortcuts = QRadioButton(_("&Default"), self)
     self.custom = QRadioButton(_("&Custom"), self)
     self.custom.toggled.connect(self.custom_toggled)
     l.addWidget(self.default_shortcuts)
     l.addWidget(self.custom)
     for which in 1, 2:
         la = QLabel(_("&Shortcut:") if which == 1 else _("&Alternate shortcut:"))
         setattr(self, 'label%d' % which, la)
         h = QHBoxLayout()
         l.addLayout(h)
         h.setContentsMargins(25, -1, -1, -1)
         h.addWidget(la)
         b = QPushButton(_("Click to change"), self)
         la.setBuddy(b)
         b.clicked.connect(partial(self.capture_clicked, which=which))
         b.installEventFilter(self)
         setattr(self, 'button%d' % which, b)
         h.addWidget(b)
         c = QToolButton(self)
         c.setIcon(QIcon(I('clear_left.png')))
         c.setToolTip(_('Clear'))
         h.addWidget(c)
         c.clicked.connect(partial(self.clear_clicked, which=which))
         setattr(self, 'clear%d' % which, c)
     self.data_model = index.model()
     self.capture = 0
     self.key = None
     self.shorcut1 = self.shortcut2 = None
     self.dup_check = dup_check
     self.custom_toggled(False)
Пример #34
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        layout = QHBoxLayout()
        layout.setSpacing(5)
        layout.setContentsMargins(0, 0, 0, 0)

        self.tags_box = EditWithComplete(parent)
        layout.addWidget(self.tags_box, stretch=1000)
        self.editor_button = QToolButton(self)
        self.editor_button.setToolTip(_('Open Item Editor'))
        self.editor_button.setIcon(QIcon(I('chapters.png')))
        layout.addWidget(self.editor_button)
        self.setLayout(layout)
Пример #35
0
    def setup_ui(self):
        self.l = l = QFormLayout(self)
        l.addRow(
            QLabel(_('Print %s to a PDF file') % elided_text(self.book_title)))
        self.h = h = QHBoxLayout()
        self.file_name = f = QLineEdit(self)
        f.setText(
            os.path.abspath(
                os.path.join(os.path.expanduser('~'), self.default_file_name)))
        self.browse_button = b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png'))), b.setToolTip(
            _('Choose location for PDF file'))
        b.clicked.connect(self.choose_file)
        h.addWidget(f), h.addWidget(b)
        f.setMinimumWidth(350)
        w = QLabel(_('&File:'))
        l.addRow(w, h), w.setBuddy(f)

        self.paper_size = ps = QComboBox(self)
        ps.addItems([
            a.upper()
            for a in sorted(self.paper_size_map, key=numeric_sort_key)
        ])
        previous_size = vprefs.get('print-to-pdf-page-size', None)
        if previous_size not in self.paper_size_map:
            previous_size = (QPrinter().pageLayout().pageSize().name()
                             or '').lower()
        if previous_size not in self.paper_size_map:
            previous_size = 'a4'
        ps.setCurrentIndex(ps.findText(previous_size.upper()))
        l.addRow(_('Paper &size:'), ps)
        tmap = {
            'left': _('&Left margin:'),
            'top': _('&Top margin:'),
            'right': _('&Right margin:'),
            'bottom': _('&Bottom margin:'),
        }
        for edge in 'left top right bottom'.split():
            m = QDoubleSpinBox(self)
            m.setSuffix(' ' + _('inches'))
            m.setMinimum(0), m.setMaximum(3), m.setSingleStep(0.1)
            val = vprefs.get('print-to-pdf-%s-margin' % edge, 1)
            m.setValue(val)
            setattr(self, '%s_margin' % edge, m)
            l.addRow(tmap[edge], m)
        self.pnum = pnum = QCheckBox(_('Add page &number to printed pages'),
                                     self)
        pnum.setChecked(vprefs.get('print-to-pdf-page-numbers', True))
        l.addRow(pnum)

        l.addRow(self.bb)
Пример #36
0
    def setup_ui(self):
        self.l = l = QFormLayout(self)
        l.setFieldGrowthPolicy(
            QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
        self.setLayout(l)

        la = self.la = QLabel(
            _('You can import an HTML or DOCX file directly as an EPUB and edit it. The EPUB'
              ' will be generated with minimal changes from the source, unlike doing a full'
              ' conversion in calibre.'))
        la.setWordWrap(True)
        l.addRow(la)

        self.h1 = h1 = QHBoxLayout()
        self.src = src = QLineEdit(self)
        src.setPlaceholderText(_('Choose the file to import'))
        h1.addWidget(src)
        self.b1 = b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png')))
        b.setText(_('Choose file'))
        h1.addWidget(b)
        l.addRow(_('Source file:'), h1)
        b.clicked.connect(self.choose_source)
        b.setFocus(Qt.FocusReason.OtherFocusReason)

        self.h2 = h1 = QHBoxLayout()
        self.dest = src = QLineEdit(self)
        src.setPlaceholderText(
            _('Choose the location for the newly created EPUB'))
        h1.addWidget(src)
        self.b2 = b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png')))
        b.setText(_('Choose file'))
        h1.addWidget(b)
        l.addRow(_('Destination file:'), h1)
        b.clicked.connect(self.choose_destination)

        l.addRow(self.bb)
Пример #37
0
    def setup_ui(self):
        self.l = l = QGridLayout(self)
        self.items = i = QListWidget(self)
        i.setSelectionMode(i.SingleSelection)
        i.currentItemChanged.connect(self.current_changed)
        l.addWidget(i)
        self.v = v = QVBoxLayout()
        l.addLayout(v, 0, 1)
        self.sort_alphabetically = sa = QCheckBox(
            _('&Sort libraries alphabetically'))
        v.addWidget(sa)
        sa.setChecked(
            bool(
                gprefs.get(
                    'copy_to_library_choose_library_sort_alphabetically',
                    True)))
        sa.stateChanged.connect(self.resort)

        connect_lambda(
            sa.stateChanged, self, lambda self: gprefs.set(
                'copy_to_library_choose_library_sort_alphabetically',
                bool(self.sort_alphabetically.isChecked())))
        la = self.la = QLabel(_('Library &path:'))
        v.addWidget(la)
        le = self.le = QLineEdit(self)
        la.setBuddy(le)
        b = self.b = QToolButton(self)
        b.setIcon(QIcon(I('document_open.png')))
        b.setToolTip(_('Browse for library'))
        b.clicked.connect(self.browse)
        h = QHBoxLayout()
        h.addWidget(le), h.addWidget(b)
        v.addLayout(h)
        v.addStretch(10)
        bb = self.bb
        bb.setStandardButtons(QDialogButtonBox.Cancel)
        self.delete_after_copy = False
        b = bb.addButton(_('&Copy'), bb.AcceptRole)
        b.setIcon(QIcon(I('edit-copy.png')))
        b.setToolTip(_('Copy to the specified library'))
        b2 = bb.addButton(_('&Move'), bb.AcceptRole)
        connect_lambda(b2.clicked, self,
                       lambda self: setattr(self, 'delete_after_copy', True))
        b2.setIcon(QIcon(I('edit-cut.png')))
        b2.setToolTip(
            _('Copy to the specified library and delete from the current library'
              ))
        b.setDefault(True)
        l.addWidget(bb, 1, 0, 1, 2)
        self.items.setFocus(Qt.OtherFocusReason)
Пример #38
0
    def __init__(self, all_formats, format_map):
        QWidget.__init__(self)
        self.l = l = QGridLayout()
        self.setLayout(l)

        self.f = f = QListWidget(self)
        l.addWidget(f, 0, 0, 3, 1)
        unchecked_formats = sorted(all_formats - set(format_map))
        for fmt in format_map + unchecked_formats:
            item = QListWidgetItem(fmt, f)
            item.setData(Qt.UserRole, fmt)
            item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
            item.setCheckState(Qt.Checked if fmt in format_map else Qt.Unchecked)

        self.button_up = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-up.png')))
        l.addWidget(b, 0, 1)
        b.clicked.connect(self.up)

        self.button_down = b = QToolButton(self)
        b.setIcon(QIcon(I('arrow-down.png')))
        l.addWidget(b, 2, 1)
        b.clicked.connect(self.down)
Пример #39
0
 def __init__(self, toc_view, parent=None):
     QWidget.__init__(self, parent)
     self.toc_view = toc_view
     self.l = l = QHBoxLayout(self)
     self.search = s = SearchBox2(self)
     self.search.setMinimumContentsLength(15)
     self.search.initialize('viewer_toc_search_history', help_text=_('Search Table of Contents'))
     self.search.setToolTip(_('Search for text in the Table of Contents'))
     s.search.connect(self.do_search)
     self.go = b = QToolButton(self)
     b.setIcon(QIcon(I('search.png')))
     b.clicked.connect(s.do_search)
     b.setToolTip(_('Find next match'))
     l.addWidget(s), l.addWidget(b)
Пример #40
0
 def mouseReleaseEvent(self, ev):
     if ev.button() == Qt.RightButton:
         from calibre.gui2.ui import get_gui
         gui = get_gui()
         if self.icname == 'search':
             gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Search'), close_after_initial=True)
             ev.accept()
             return
         tab_name = {'book':'book_details', 'grid':'cover_grid', 'cover_flow':'cover_browser', 'tags':'tag_browser'}.get(self.icname)
         if tab_name:
             if gui is not None:
                 gui.iactions['Preferences'].do_config(initial_plugin=('Interface', 'Look & Feel', tab_name+'_tab'), close_after_initial=True)
                 ev.accept()
                 return
     return QToolButton.mouseReleaseEvent(self, ev)
Пример #41
0
    def make_widgets(self, parent, main_widget_class, extra_label_text='',
                     add_tags_edit_button=False):
        w = QWidget(parent)
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
        l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        w.setLayout(l)
        self.main_widget = main_widget_class(w)
        l.addWidget(self.main_widget)
        l.setStretchFactor(self.main_widget, 10)
        if add_tags_edit_button:
            self.edit_tags_button = QToolButton(parent)
            self.edit_tags_button.setToolTip(_('Open Item Editor'))
            self.edit_tags_button.setIcon(QIcon(I('chapters.png')))
            l.addWidget(self.edit_tags_button)
        self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
        l.addWidget(self.a_c_checkbox)
        self.ignore_change_signals = True

        # connect to the various changed signals so we can auto-update the
        # apply changes checkbox
        if hasattr(self.main_widget, 'editTextChanged'):
            # editable combobox widgets
            self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'textChanged'):
            # lineEdit widgets
            self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'currentIndexChanged'):
            # combobox widgets
            self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'valueChanged'):
            # spinbox widgets
            self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'dateTimeChanged'):
            # dateEdit widgets
            self.main_widget.dateTimeChanged.connect(self.a_c_checkbox_changed)
Пример #42
0
 def create_color_button(key, text):
     b = ColorButton(data, key, text, self)
     b.changed.connect(self.changed), l.addWidget(b)
     bc = QToolButton(self)
     bc.setIcon(QIcon(I('clear_left.png')))
     bc.setToolTip(_('Remove color'))
     bc.clicked.connect(b.clear)
     h = QHBoxLayout()
     h.addWidget(b), h.addWidget(bc)
     return h
Пример #43
0
    def __init__(self, name, layout):
        QWidget.__init__(self)
        self.dname = name
        opt = options[name]
        self.l = l = QHBoxLayout(self)
        l.setContentsMargins(0, 0, 0, 0)
        self.text = t = QLineEdit(self)
        t.setClearButtonEnabled(True)
        t.textChanged.connect(self.changed_signal.emit)
        l.addWidget(t)

        self.b = b = QToolButton(self)
        l.addWidget(b)
        b.setIcon(QIcon(I('document_open.png')))
        b.setToolTip(_("Browse for the file"))
        b.clicked.connect(self.choose)
        init_opt(self, opt, layout)
Пример #44
0
    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)
        self.action = a = QComboBox(self)
        h.addWidget(a)
        for action, text in iteritems(self.ACTION_MAP):
            a.addItem(text, action)
        a.currentIndexChanged.connect(self.update_state)
        self.la1 = la = QLabel('\xa0' + self.SUBJECT + '\xa0')
        h.addWidget(la)
        self.match_type = q = QComboBox(self)
        h.addWidget(q)
        for action, text in iteritems(self.MATCH_TYPE_MAP):
            q.addItem(text, action)
        q.currentIndexChanged.connect(self.update_state)
        self.la2 = la = QLabel(':\xa0')
        h.addWidget(la)
        self.query = q = QueryEdit(self)
        h.addWidget(q)
        self.tag_editor_button = b = QToolButton(self)
        b.setIcon(QIcon(I('chapters.png')))
        b.setToolTip(_('Edit the list of tags with the Tag editor'))
        h.addWidget(b), b.clicked.connect(self.edit_tags)
        b.setVisible(self.can_use_tag_editor)
        self.h2 = h = QHBoxLayout()
        l.addLayout(h)
        self.la3 = la = QLabel(self.REPLACE_TEXT + '\xa0')
        h.addWidget(la)
        self.replace = r = QLineEdit(self)
        h.addWidget(r)
        self.regex_help = la = QLabel(
            '<p>' + self.REGEXP_HELP_TEXT % localize_user_manual_link(
                'https://manual.calibre-ebook.com/regexp.html'))
        la.setOpenExternalLinks(True)
        la.setWordWrap(True)
        l.addWidget(la)
        la.setVisible(False)
        l.addStretch(10)
        self.la3.setVisible(False), self.replace.setVisible(False)
        self.update_state()
Пример #45
0
 def __init__(self, val, device):
     QWidget.__init__(self)
     self.t = t = QLineEdit(self)
     t.setText(', '.join(val or []))
     t.setCursorPosition(0)
     self.l = l = QGridLayout(self)
     self.setLayout(l)
     self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to
     which to send ebooks. The first one that exists will be used:'''))
     m.setWordWrap(True)
     m.setBuddy(t)
     l.addWidget(m, 0, 0, 1, 2)
     l.addWidget(t, 1, 0)
     self.b = b = QToolButton()
     l.addWidget(b, 1, 1)
     b.setIcon(QIcon(I('document_open.png')))
     b.clicked.connect(self.browse)
     b.setToolTip(_('Browse for a folder on the device'))
     self._device = weakref.ref(device)
Пример #46
0
 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.Fixed, QSizePolicy.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)
Пример #47
0
    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
Пример #48
0
    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)
        self.action = a = QComboBox(self)
        h.addWidget(a)
        for action, text in self.ACTION_MAP.iteritems():
            a.addItem(text, action)
        a.currentIndexChanged.connect(self.update_state)
        self.la1 = la = QLabel('\xa0' + self.SUBJECT + '\xa0')
        h.addWidget(la)
        self.match_type = q = QComboBox(self)
        h.addWidget(q)
        for action, text in self.MATCH_TYPE_MAP.iteritems():
            q.addItem(text, action)
        q.currentIndexChanged.connect(self.update_state)
        self.la2 = la = QLabel(':\xa0')
        h.addWidget(la)
        self.query = q = QueryEdit(self)
        h.addWidget(q)
        self.tag_editor_button = b = QToolButton(self)
        b.setIcon(QIcon(I('chapters.png')))
        b.setToolTip(_('Edit the list of tags with the tag editor'))
        h.addWidget(b), b.clicked.connect(self.edit_tags)
        b.setVisible(self.can_use_tag_editor)
        self.h2 = h = QHBoxLayout()
        l.addLayout(h)
        self.la3 = la = QLabel(_('with the tag:') + '\xa0')
        h.addWidget(la)
        self.replace = r = QLineEdit(self)
        h.addWidget(r)
        l.addStretch(10)
        self.la3.setVisible(False), self.replace.setVisible(False)
        self.update_state()
Пример #49
0
 def setup_ui(self):
     self.l = l = QVBoxLayout(self)
     self.recipes = r = QTreeView(self)
     r.setAnimated(True)
     r.setHeaderHidden(True)
     self.model = ChooseBuiltinRecipeModel(self)
     self.model.setSourceModel(self.recipe_model)
     r.setModel(self.model)
     r.doubleClicked.connect(self.accept)
     self.search = s = SearchBox2(self)
     self.search.initialize('scheduler_search_history')
     self.search.setMinimumContentsLength(15)
     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)
     self.go_button = b = QToolButton(self)
     b.setText(_("Go"))
     b.clicked.connect(self.search.do_search)
     h = QHBoxLayout()
     h.addWidget(s), h.addWidget(b)
     l.addLayout(h)
     l.addWidget(self.recipes)
     l.addWidget(self.bb)
     self.search.setFocus(Qt.FocusReason.OtherFocusReason)
Пример #50
0
 def __init__(self, index, dup_check, parent=None):
     QFrame.__init__(self, parent)
     self.setFrameShape(self.StyledPanel)
     self.setFrameShadow(self.Raised)
     self.setFocusPolicy(Qt.StrongFocus)
     self.setAutoFillBackground(True)
     self.l = l = QVBoxLayout(self)
     self.header = la = QLabel(self)
     la.setWordWrap(True)
     l.addWidget(la)
     self.default_shortcuts = QRadioButton(_("&Default"), self)
     self.custom = QRadioButton(_("&Custom"), self)
     self.custom.toggled.connect(self.custom_toggled)
     l.addWidget(self.default_shortcuts)
     l.addWidget(self.custom)
     for which in 1, 2:
         la = QLabel(
             _("&Shortcut:") if which == 1 else _("&Alternate shortcut:"))
         setattr(self, 'label%d' % which, la)
         h = QHBoxLayout()
         l.addLayout(h)
         h.setContentsMargins(25, -1, -1, -1)
         h.addWidget(la)
         b = QPushButton(_("Click to change"), self)
         la.setBuddy(b)
         b.clicked.connect(partial(self.capture_clicked, which=which))
         b.installEventFilter(self)
         setattr(self, 'button%d' % which, b)
         h.addWidget(b)
         c = QToolButton(self)
         c.setIcon(QIcon(I('clear_left.png')))
         c.setToolTip(_('Clear'))
         h.addWidget(c)
         c.clicked.connect(partial(self.clear_clicked, which=which))
         setattr(self, 'clear%d' % which, c)
     self.data_model = index.model()
     self.capture = 0
     self.key = None
     self.shorcut1 = self.shortcut2 = None
     self.dup_check = dup_check
     self.custom_toggled(False)
Пример #51
0
class TagBrowserWidget(QWidget):  # {{{

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.parent = parent
        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0,0,0,0)

        # Set up the find box & button
        search_layout = QHBoxLayout()
        self._layout.addLayout(search_layout)
        self.item_search = HistoryLineEdit(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        try:
            self.item_search.lineEdit().setPlaceholderText(
                                                _('Find item in tag browser'))
        except:
            pass             # Using Qt < 4.7
        self.item_search.setToolTip(_(
        'Search for items. This is a "contains" search; items containing the\n'
        'text anywhere in the name will be found. You can limit the search\n'
        'to particular categories using syntax similar to search. For example,\n'
        'tags:foo will find foo in any tag, but not in authors etc. Entering\n'
        '*foo will filter all categories at once, showing only those items\n'
        'containing the text "foo"'))
        search_layout.addWidget(self.item_search)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find box',
                _('Find item'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setText(_('Find'))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        search_layout.addWidget(self.search_button)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find button',
                _('Find button'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.search_button.click)

        self.expand_button = QToolButton()
        self.expand_button.setText('-')
        self.expand_button.setToolTip(_('Collapse all categories'))
        search_layout.addWidget(self.expand_button)
        search_layout.setStretch(0, 10)
        search_layout.setStretch(1, 1)
        search_layout.setStretch(2, 1)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser collapse all',
                _('Collapse all'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.expand_button.clicked)

        self.current_find_position = None
        self.search_button.clicked.connect(self.find)
        self.item_search.initialize('tag_browser_search')
        self.item_search.lineEdit().returnPressed.connect(self.do_find)
        self.item_search.lineEdit().textEdited.connect(self.find_text_changed)
        self.item_search.activated[str].connect(self.do_find)
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)

        parent.tags_view = TagsView(parent)
        self.tags_view = parent.tags_view
        self.expand_button.clicked.connect(self.tags_view.collapseAll)
        self._layout.addWidget(parent.tags_view)

        # Now the floating 'not found' box
        l = QLabel(self.tags_view)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText('<p><b>'+_('No More Matches.</b><p> Click Find again to go to first match'))
        l.setAlignment(Qt.AlignVCenter)
        l.setWordWrap(True)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(self.not_found_label_timer_event,
                                                   type=Qt.QueuedConnection)

        parent.alter_tb = l = QPushButton(parent)
        l.setText(_('Alter Tag Browser'))
        l.setIcon(QIcon(I('tags.png')))
        l.m = QMenu()
        l.setMenu(l.m)
        self._layout.addWidget(l)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser alter',
                _('Alter tag browser'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(l.showMenu)

        sb = l.m.addAction(_('Sort by'))
        sb.m = l.sort_menu = QMenu(l.m)
        sb.setMenu(sb.m)
        sb.bg = QActionGroup(sb)

        # Must be in the same order as db2.CATEGORY_SORTS
        for i, x in enumerate((_('Sort by name'), _('Sort by number of books'),
                  _('Sort by average rating'))):
            a = sb.m.addAction(x)
            sb.bg.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        sb.setToolTip(
                _('Set the sort order for entries in the Tag Browser'))
        sb.setStatusTip(sb.toolTip())

        ma = l.m.addAction(_('Search type when selecting multiple items'))
        ma.m = l.match_menu = QMenu(l.m)
        ma.setMenu(ma.m)
        ma.ag = QActionGroup(ma)

        # Must be in the same order as db2.MATCH_TYPE
        for i, x in enumerate((_('Match any of the items'), _('Match all of the items'))):
            a = ma.m.addAction(x)
            ma.ag.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        ma.setToolTip(
                _('When selecting multiple entries in the Tag Browser '
                    'match any or all of them'))
        ma.setStatusTip(ma.toolTip())

        mt = l.m.addAction(_('Manage authors, tags, etc.'))
        mt.setToolTip(_('All of these category_managers are available by right-clicking '
                       'on items in the tag browser above'))
        mt.m = l.manage_menu = QMenu(l.m)
        mt.setMenu(mt.m)

        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser toggle item',
                _("'Click' found item"), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.toggle_item)

        # self.leak_test_timer = QTimer(self)
        # self.leak_test_timer.timeout.connect(self.test_for_leak)
        # self.leak_test_timer.start(5000)

    def toggle_item(self):
        self.tags_view.toggle_current_index()

    def set_pane_is_visible(self, to_what):
        self.tags_view.set_pane_is_visible(to_what)

    def find_text_changed(self, str):
        self.current_find_position = None

    def set_focus_to_find_box(self):
        self.item_search.setFocus()
        self.item_search.lineEdit().selectAll()

    def do_find(self, str=None):
        self.current_find_position = None
        self.find()

    def find(self):
        model = self.tags_view.model()
        model.clear_boxed()
        txt = unicode(self.item_search.currentText()).strip()

        if txt.startswith('*'):
            model.set_categories_filter(txt[1:])
            self.tags_view.recount()
            self.current_find_position = None
            return
        if model.get_categories_filter():
            model.set_categories_filter(None)
            self.tags_view.recount()
            self.current_find_position = None

        if not txt:
            return

        self.item_search.lineEdit().blockSignals(True)
        self.search_button.setFocus(True)
        self.item_search.lineEdit().blockSignals(False)

        key = None
        colon = txt.rfind(':') if len(txt) > 2 else 0
        if colon > 0:
            key = self.parent.library_view.model().db.\
                        field_metadata.search_term_to_field_key(txt[:colon])
            txt = txt[colon+1:]

        self.current_find_position = \
            model.find_item_node(key, txt, self.current_find_position)

        if self.current_find_position:
            self.tags_view.show_item_at_path(self.current_find_position, box=True)
        elif self.item_search.text():
            self.not_found_label.setVisible(True)
            if self.tags_view.verticalScrollBar().isVisible():
                sbw = self.tags_view.verticalScrollBar().width()
            else:
                sbw = 0
            width = self.width() - 8 - sbw
            height = self.not_found_label.heightForWidth(width) + 20
            self.not_found_label.resize(width, height)
            self.not_found_label.move(4, 10)
            self.not_found_label_timer.start(2000)

    def not_found_label_timer_event(self):
        self.not_found_label.setVisible(False)
Пример #52
0
class BulkBase(Base):

    @property
    def gui_val(self):
        if not hasattr(self, '_cached_gui_val_'):
            self._cached_gui_val_ = self.getter()
        return self._cached_gui_val_

    def get_initial_value(self, book_ids):
        values = set([])
        for book_id in book_ids:
            val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
            if isinstance(val, list):
                val = frozenset(val)
            values.add(val)
            if len(values) > 1:
                break
        ans = None
        if len(values) == 1:
            ans = iter(values).next()
        if isinstance(ans, frozenset):
            ans = list(ans)
        return ans

    def initialize(self, book_ids):
        self.initial_val = val = self.get_initial_value(book_ids)
        val = self.normalize_db_val(val)
        self.setter(val)

    def commit(self, book_ids, notify=False):
        if not self.a_c_checkbox.isChecked():
            return
        val = self.gui_val
        val = self.normalize_ui_val(val)
        self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)

    def make_widgets(self, parent, main_widget_class, extra_label_text='',
                     add_tags_edit_button=False):
        w = QWidget(parent)
        self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
        l = QHBoxLayout()
        l.setContentsMargins(0, 0, 0, 0)
        w.setLayout(l)
        self.main_widget = main_widget_class(w)
        l.addWidget(self.main_widget)
        l.setStretchFactor(self.main_widget, 10)
        if add_tags_edit_button:
            self.edit_tags_button = QToolButton(parent)
            self.edit_tags_button.setToolTip(_('Open Item Editor'))
            self.edit_tags_button.setIcon(QIcon(I('chapters.png')))
            l.addWidget(self.edit_tags_button)
        self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
        l.addWidget(self.a_c_checkbox)
        self.ignore_change_signals = True

        # connect to the various changed signals so we can auto-update the
        # apply changes checkbox
        if hasattr(self.main_widget, 'editTextChanged'):
            # editable combobox widgets
            self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'textChanged'):
            # lineEdit widgets
            self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'currentIndexChanged'):
            # combobox widgets
            self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'valueChanged'):
            # spinbox widgets
            self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
        if hasattr(self.main_widget, 'dateTimeChanged'):
            # dateEdit widgets
            self.main_widget.dateTimeChanged.connect(self.a_c_checkbox_changed)

    def a_c_checkbox_changed(self):
        if not self.ignore_change_signals:
            self.a_c_checkbox.setChecked(True)
Пример #53
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.parent = parent
        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0,0,0,0)

        # Set up the find box & button
        search_layout = QHBoxLayout()
        self._layout.addLayout(search_layout)
        self.item_search = HistoryLineEdit(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        try:
            self.item_search.lineEdit().setPlaceholderText(
                                                _('Find item in tag browser'))
        except:
            pass             # Using Qt < 4.7
        self.item_search.setToolTip(_(
        'Search for items. This is a "contains" search; items containing the\n'
        'text anywhere in the name will be found. You can limit the search\n'
        'to particular categories using syntax similar to search. For example,\n'
        'tags:foo will find foo in any tag, but not in authors etc. Entering\n'
        '*foo will filter all categories at once, showing only those items\n'
        'containing the text "foo"'))
        search_layout.addWidget(self.item_search)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find box',
                _('Find item'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setText(_('Find'))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        search_layout.addWidget(self.search_button)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser find button',
                _('Find button'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.search_button.click)

        self.expand_button = QToolButton()
        self.expand_button.setText('-')
        self.expand_button.setToolTip(_('Collapse all categories'))
        search_layout.addWidget(self.expand_button)
        search_layout.setStretch(0, 10)
        search_layout.setStretch(1, 1)
        search_layout.setStretch(2, 1)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser collapse all',
                _('Collapse all'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.expand_button.clicked)

        self.current_find_position = None
        self.search_button.clicked.connect(self.find)
        self.item_search.initialize('tag_browser_search')
        self.item_search.lineEdit().returnPressed.connect(self.do_find)
        self.item_search.lineEdit().textEdited.connect(self.find_text_changed)
        self.item_search.activated[str].connect(self.do_find)
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)

        parent.tags_view = TagsView(parent)
        self.tags_view = parent.tags_view
        self.expand_button.clicked.connect(self.tags_view.collapseAll)
        self._layout.addWidget(parent.tags_view)

        # Now the floating 'not found' box
        l = QLabel(self.tags_view)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText('<p><b>'+_('No More Matches.</b><p> Click Find again to go to first match'))
        l.setAlignment(Qt.AlignVCenter)
        l.setWordWrap(True)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(self.not_found_label_timer_event,
                                                   type=Qt.QueuedConnection)

        parent.alter_tb = l = QPushButton(parent)
        l.setText(_('Alter Tag Browser'))
        l.setIcon(QIcon(I('tags.png')))
        l.m = QMenu()
        l.setMenu(l.m)
        self._layout.addWidget(l)
        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser alter',
                _('Alter tag browser'), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(l.showMenu)

        sb = l.m.addAction(_('Sort by'))
        sb.m = l.sort_menu = QMenu(l.m)
        sb.setMenu(sb.m)
        sb.bg = QActionGroup(sb)

        # Must be in the same order as db2.CATEGORY_SORTS
        for i, x in enumerate((_('Sort by name'), _('Sort by number of books'),
                  _('Sort by average rating'))):
            a = sb.m.addAction(x)
            sb.bg.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        sb.setToolTip(
                _('Set the sort order for entries in the Tag Browser'))
        sb.setStatusTip(sb.toolTip())

        ma = l.m.addAction(_('Search type when selecting multiple items'))
        ma.m = l.match_menu = QMenu(l.m)
        ma.setMenu(ma.m)
        ma.ag = QActionGroup(ma)

        # Must be in the same order as db2.MATCH_TYPE
        for i, x in enumerate((_('Match any of the items'), _('Match all of the items'))):
            a = ma.m.addAction(x)
            ma.ag.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        ma.setToolTip(
                _('When selecting multiple entries in the Tag Browser '
                    'match any or all of them'))
        ma.setStatusTip(ma.toolTip())

        mt = l.m.addAction(_('Manage authors, tags, etc.'))
        mt.setToolTip(_('All of these category_managers are available by right-clicking '
                       'on items in the tag browser above'))
        mt.m = l.manage_menu = QMenu(l.m)
        mt.setMenu(mt.m)

        ac = QAction(parent)
        parent.addAction(ac)
        parent.keyboard.register_shortcut('tag browser toggle item',
                _("'Click' found item"), default_keys=(),
                action=ac, group=_('Tag Browser'))
        ac.triggered.connect(self.toggle_item)
Пример #54
0
class MetadataSingleDialogBase(ResizableDialog):

    view_format = pyqtSignal(object, object)
    cc_two_column = tweaks['metadata_single_use_2_cols_for_custom_fields']
    one_line_comments_toolbar = False
    use_toolbutton_for_config_metadata = True

    def __init__(self, db, parent=None, editing_multiple=False):
        self.db = db
        self.changed = set()
        self.books_to_refresh = set()
        self.rows_to_refresh = set()
        self.metadata_before_fetch = None
        self.editing_multiple = editing_multiple
        self.comments_edit_state_at_apply = {}
        ResizableDialog.__init__(self, parent)

    def setupUi(self, *args):  # {{{
        self.resize(990, 670)

        self.download_shortcut = QShortcut(self)
        self.download_shortcut.setKey(QKeySequence('Ctrl+D',
            QKeySequence.PortableText))
        p = self.parent()
        if hasattr(p, 'keyboard'):
            kname = u'Interface Action: Edit Metadata (Edit Metadata) : menu action : download'
            sc = p.keyboard.keys_map.get(kname, None)
            if sc:
                self.download_shortcut.setKey(sc[0])
        self.swap_title_author_shortcut = s = QShortcut(self)
        s.setKey(QKeySequence('Alt+Down', QKeySequence.PortableText))

        self.button_box = bb = QDialogButtonBox(self)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
                self)
        self.next_button.setShortcut(QKeySequence('Alt+Right'))
        self.next_button.clicked.connect(self.next_clicked)
        self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
                self)
        self.prev_button.setShortcut(QKeySequence('Alt+Left'))

        self.button_box.addButton(self.prev_button, bb.ActionRole)
        self.button_box.addButton(self.next_button, bb.ActionRole)
        self.prev_button.clicked.connect(self.prev_clicked)
        bb.setStandardButtons(bb.Ok|bb.Cancel)
        bb.button(bb.Ok).setDefault(True)

        self.scroll_area = QScrollArea(self)
        self.scroll_area.setFrameShape(QScrollArea.NoFrame)
        self.scroll_area.setWidgetResizable(True)
        self.central_widget = QTabWidget(self)
        self.scroll_area.setWidget(self.central_widget)

        self.l = QVBoxLayout(self)
        self.setLayout(self.l)
        self.l.addWidget(self.scroll_area)
        ll = self.button_box_layout = QHBoxLayout()
        self.l.addLayout(ll)
        ll.addSpacing(10)
        ll.addWidget(self.button_box)

        self.setWindowIcon(QIcon(I('edit_input.png')))
        self.setWindowTitle(BASE_TITLE)

        self.create_basic_metadata_widgets()

        if len(self.db.custom_column_label_map):
            self.create_custom_metadata_widgets()
        self.comments_edit_state_at_apply = {self.comments:None}

        self.do_layout()
        geom = gprefs.get('metasingle_window_geometry3', None)
        if geom is not None:
            self.restoreGeometry(bytes(geom))
    # }}}

    def create_basic_metadata_widgets(self):  # {{{
        self.basic_metadata_widgets = []

        self.languages = LanguagesEdit(self)
        self.basic_metadata_widgets.append(self.languages)

        self.title = TitleEdit(self)
        self.title.textChanged.connect(self.update_window_title)
        self.deduce_title_sort_button = QToolButton(self)
        self.deduce_title_sort_button.setToolTip(
            _('Automatically create the title sort entry based on the current '
                'title entry.\nUsing this button to create title sort will '
                'change title sort from red to green.'))
        self.deduce_title_sort_button.setWhatsThis(
                self.deduce_title_sort_button.toolTip())
        self.title_sort = TitleSortEdit(self, self.title,
                self.deduce_title_sort_button, self.languages)
        self.basic_metadata_widgets.extend([self.title, self.title_sort])

        self.deduce_author_sort_button = b = RightClickButton(self)
        b.setToolTip('<p>' +
            _('Automatically create the author sort entry based on the current '
              'author entry. Using this button to create author sort will '
              'change author sort from red to green.  There is a menu of '
              'functions available under this button. Click and hold '
              'on the button to see it.') + '</p>')
        if isosx:
            # Workaround for https://bugreports.qt-project.org/browse/QTBUG-41017
            class Menu(QMenu):

                def mouseReleaseEvent(self, ev):
                    ac = self.actionAt(ev.pos())
                    if ac is not None:
                        ac.trigger()
                    return QMenu.mouseReleaseEvent(self, ev)
            b.m = m = Menu()
        else:
            b.m = m = QMenu()
        ac = m.addAction(QIcon(I('forward.png')), _('Set author sort from author'))
        ac2 = m.addAction(QIcon(I('back.png')), _('Set author from author sort'))
        ac3 = m.addAction(QIcon(I('user_profile.png')), _('Manage authors'))
        ac4 = m.addAction(QIcon(I('next.png')),
                _('Copy author to author sort'))
        ac5 = m.addAction(QIcon(I('previous.png')),
                _('Copy author sort to author'))

        b.setMenu(m)
        self.authors = AuthorsEdit(self, ac3)
        self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac,
                ac2, ac4, ac5)
        self.basic_metadata_widgets.extend([self.authors, self.author_sort])

        self.swap_title_author_button = QToolButton(self)
        self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
        self.swap_title_author_button.setToolTip(_(
            'Swap the author and title') + ' [%s]' % self.swap_title_author_shortcut.key().toString(QKeySequence.NativeText))
        self.swap_title_author_button.clicked.connect(self.swap_title_author)
        self.swap_title_author_shortcut.activated.connect(self.swap_title_author_button.click)

        self.manage_authors_button = QToolButton(self)
        self.manage_authors_button.setIcon(QIcon(I('user_profile.png')))
        self.manage_authors_button.setToolTip('<p>' + _(
            'Manage authors. Use to rename authors and correct '
            'individual author\'s sort values') + '</p>')
        self.manage_authors_button.clicked.connect(self.authors.manage_authors)

        self.series = SeriesEdit(self)
        self.clear_series_button = QToolButton(self)
        self.clear_series_button.setToolTip(
               _('Clear series'))
        self.clear_series_button.clicked.connect(self.series.clear)
        self.series_index = SeriesIndexEdit(self, self.series)
        self.basic_metadata_widgets.extend([self.series, self.series_index])

        self.formats_manager = FormatsManager(self, self.copy_fmt)
        # We want formats changes to be committed before title/author, as
        # otherwise we could have data loss if the title/author changed and the
        # user was trying to add an extra file from the old books directory.
        self.basic_metadata_widgets.insert(0, self.formats_manager)
        self.formats_manager.metadata_from_format_button.clicked.connect(
                self.metadata_from_format)
        self.formats_manager.cover_from_format_button.clicked.connect(
                self.cover_from_format)
        self.cover = Cover(self)
        self.cover.download_cover.connect(self.download_cover)
        self.basic_metadata_widgets.append(self.cover)

        self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
        self.basic_metadata_widgets.append(self.comments)

        self.rating = RatingEdit(self)
        self.clear_ratings_button = QToolButton(self)
        self.clear_ratings_button.setToolTip(_('Clear rating'))
        self.clear_ratings_button.setIcon(QIcon(I('trash.png')))
        self.clear_ratings_button.clicked.connect(self.rating.zero)

        self.basic_metadata_widgets.append(self.rating)

        self.tags = TagsEdit(self)
        self.tags_editor_button = QToolButton(self)
        self.tags_editor_button.setToolTip(_('Open Tag Editor'))
        self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
        self.tags_editor_button.clicked.connect(self.tags_editor)
        self.clear_tags_button = QToolButton(self)
        self.clear_tags_button.setToolTip(_('Clear all tags'))
        self.clear_tags_button.setIcon(QIcon(I('trash.png')))
        self.clear_tags_button.clicked.connect(self.tags.clear)
        self.basic_metadata_widgets.append(self.tags)

        self.identifiers = IdentifiersEdit(self)
        self.basic_metadata_widgets.append(self.identifiers)
        self.clear_identifiers_button = QToolButton(self)
        self.clear_identifiers_button.setIcon(QIcon(I('trash.png')))
        self.clear_identifiers_button.setToolTip(_('Clear Ids'))
        self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
        self.paste_isbn_button = QToolButton(self)
        self.paste_isbn_button.setToolTip('<p>' +
                    _('Paste the contents of the clipboard into the '
                      'identifiers box prefixed with isbn:') + '</p>')
        self.paste_isbn_button.setIcon(QIcon(I('edit-paste.png')))
        self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn)

        self.publisher = PublisherEdit(self)
        self.basic_metadata_widgets.append(self.publisher)

        self.timestamp = DateEdit(self)
        self.pubdate = PubdateEdit(self)
        self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])

        self.fetch_metadata_button = b = RightClickButton(self)
        # The following rigmarole is needed so that Qt gives the button the
        # same height as the other buttons in the dialog. There is no way to
        # center the text in a QToolButton with an icon, so we cant just set an
        # icon
        b.setIcon(QIcon(I('download-metadata.png')))
        b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setMinimumHeight(b.sizeHint().height())
        b.setIcon(QIcon())
        b.setText(_('&Download metadata')), b.setPopupMode(b.DelayedPopup)
        b.setToolTip(_('Download metadata for this book [%s]') % self.download_shortcut.key().toString(QKeySequence.NativeText))
        b.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
        self.fetch_metadata_menu = m = QMenu(self.fetch_metadata_button)
        m.addAction(QIcon(I('edit-undo.png')), _('Undo last metadata download'), self.undo_fetch_metadata)
        self.fetch_metadata_button.setMenu(m)
        self.download_shortcut.activated.connect(self.fetch_metadata_button.click)
        font = self.fmb_font = QFont()
        font.setBold(True)
        self.fetch_metadata_button.setFont(font)

        if self.use_toolbutton_for_config_metadata:
            self.config_metadata_button = QToolButton(self)
            self.config_metadata_button.setIcon(QIcon(I('config.png')))
        else:
            self.config_metadata_button = QPushButton(self)
            self.config_metadata_button.setText(_('Configure download metadata'))
        self.config_metadata_button.setIcon(QIcon(I('config.png')))
        self.config_metadata_button.clicked.connect(self.configure_metadata)
        self.config_metadata_button.setToolTip(
            _('Change how calibre downloads metadata'))

    # }}}

    def create_custom_metadata_widgets(self):  # {{{
        self.custom_metadata_widgets_parent = w = QWidget(self)
        layout = QGridLayout()
        w.setLayout(layout)
        self.custom_metadata_widgets, self.__cc_spacers = \
            populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
                two_column=self.cc_two_column)
        self.__custom_col_layouts = [layout]
        for widget in self.custom_metadata_widgets:
            if isinstance(widget, Comments):
                self.comments_edit_state_at_apply[widget] = None
    # }}}

    def set_custom_metadata_tab_order(self, before=None, after=None):  # {{{
        sto = QWidget.setTabOrder
        if getattr(self, 'custom_metadata_widgets', []):
            ans = self.custom_metadata_widgets
            for i in range(len(ans)-1):
                if before is not None and i == 0:
                    pass
                if len(ans[i+1].widgets) == 2:
                    sto(ans[i].widgets[-1], ans[i+1].widgets[1])
                else:
                    sto(ans[i].widgets[-1], ans[i+1].widgets[0])
                for c in range(2, len(ans[i].widgets), 2):
                    sto(ans[i].widgets[c-1], ans[i].widgets[c+1])
            if after is not None:
                pass
    # }}}

    def do_view_format(self, path, fmt):
        if path:
            self.view_format.emit(None, path)
        else:
            self.view_format.emit(self.book_id, fmt)

    def copy_fmt(self, fmt, f):
        self.db.copy_format_to(self.book_id, fmt, f, index_is_id=True)

    def do_layout(self):
        raise NotImplementedError()

    def __call__(self, id_):
        self.book_id = id_
        self.books_to_refresh = set([])
        self.metadata_before_fetch = None
        for widget in self.basic_metadata_widgets:
            widget.initialize(self.db, id_)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.initialize(id_)
        if callable(self.set_current_callback):
            self.set_current_callback(id_)
        # Commented out as it doesn't play nice with Next, Prev buttons
        # self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)

    # Miscellaneous interaction methods {{{
    def update_window_title(self, *args):
        title = self.title.current_val
        if len(title) > 50:
            title = title[:50] + u'\u2026'
        self.setWindowTitle(BASE_TITLE + ' - ' +
                title + ' - ' +
                _(' [%(num)d of %(tot)d]')%dict(num=self.current_row+1,
                tot=len(self.row_list)))

    def swap_title_author(self, *args):
        title = self.title.current_val
        self.title.current_val = authors_to_string(self.authors.current_val)
        self.authors.current_val = string_to_authors(title)
        self.title_sort.auto_generate()
        self.author_sort.auto_generate()

    def tags_editor(self, *args):
        self.tags.edit(self.db, self.book_id)

    def metadata_from_format(self, *args):
        mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
                self.book_id)
        if mi is not None:
            self.update_from_mi(mi)

    def get_pdf_cover(self):
        pdfpath = self.formats_manager.get_format_path(self.db, self.book_id,
                                                       'pdf')
        from calibre.gui2.metadata.pdf_covers import PDFCovers
        d = PDFCovers(pdfpath, parent=self)
        if d.exec_() == d.Accepted:
            cpath = d.cover_path
            if cpath:
                with open(cpath, 'rb') as f:
                    self.update_cover(f.read(), 'PDF')
        d.cleanup()

    def cover_from_format(self, *args):
        ext = self.formats_manager.get_selected_format()
        if ext is None:
            return
        if ext == 'pdf':
            return self.get_pdf_cover()
        try:
            mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
                    self.book_id)
        except (IOError, OSError) as err:
            if getattr(err, 'errno', None) == errno.EACCES:  # Permission denied
                import traceback
                fname = err.filename if err.filename else 'file'
                error_dialog(self, _('Permission denied'),
                        _('Could not open %s. Is it being used by another'
                        ' program?')%fname, det_msg=traceback.format_exc(),
                        show=True)
                return
            raise
        if mi is None:
            return
        cdata = None
        if mi.cover and os.access(mi.cover, os.R_OK):
            cdata = open(mi.cover).read()
        elif mi.cover_data[1] is not None:
            cdata = mi.cover_data[1]
        if cdata is None:
            error_dialog(self, _('Could not read cover'),
                         _('Could not read cover from %s format')%ext).exec_()
            return
        self.update_cover(cdata, ext)

    def update_cover(self, cdata, fmt):
        orig = self.cover.current_val
        self.cover.current_val = cdata
        if self.cover.current_val is None:
            self.cover.current_val = orig
            return error_dialog(self, _('Could not read cover'),
                         _('The cover in the %s format is invalid')%fmt,
                         show=True)
            return

    def update_from_mi(self, mi, update_sorts=True, merge_tags=True, merge_comments=False):
        fw = self.focusWidget()
        if not mi.is_null('title'):
            self.title.set_value(mi.title)
            if update_sorts:
                self.title_sort.auto_generate()
        if not mi.is_null('authors'):
            self.authors.set_value(mi.authors)
        if not mi.is_null('author_sort'):
            self.author_sort.set_value(mi.author_sort)
        elif update_sorts and not mi.is_null('authors'):
            self.author_sort.auto_generate()
        if not mi.is_null('rating'):
            try:
                self.rating.set_value(mi.rating)
            except:
                pass
        if not mi.is_null('publisher'):
            self.publisher.set_value(mi.publisher)
        if not mi.is_null('tags'):
            old_tags = self.tags.current_val
            tags = mi.tags if mi.tags else []
            if old_tags and merge_tags:
                ltags, lotags = {t.lower() for t in tags}, {t.lower() for t in
                        old_tags}
                tags = [t for t in tags if t.lower() in ltags-lotags] + old_tags
            self.tags.set_value(tags)
        if not mi.is_null('identifiers'):
            current = self.identifiers.current_val
            current.update(mi.identifiers)
            self.identifiers.set_value(current)
        if not mi.is_null('pubdate'):
            self.pubdate.set_value(mi.pubdate)
        if not mi.is_null('series') and mi.series.strip():
            self.series.set_value(mi.series)
            if mi.series_index is not None:
                self.series_index.reset_original()
                self.series_index.set_value(float(mi.series_index))
        if not mi.is_null('languages'):
            langs = [canonicalize_lang(x) for x in mi.languages]
            langs = [x for x in langs if x is not None]
            if langs:
                self.languages.set_value(langs)
        if mi.comments and mi.comments.strip():
            val = mi.comments
            if val and merge_comments:
                cval = self.comments.current_val
                if cval:
                    val = merge_two_comments(cval, val)
            self.comments.set_value(val)
        if fw is not None:
            fw.setFocus(Qt.OtherFocusReason)

    def fetch_metadata(self, *args):
        d = FullFetch(self.cover.pixmap(), self)
        ret = d.start(title=self.title.current_val, authors=self.authors.current_val,
                identifiers=self.identifiers.current_val)
        if ret == d.Accepted:
            self.metadata_before_fetch = {f:getattr(self, f).current_val for f in fetched_fields}
            from calibre.ebooks.metadata.sources.prefs import msprefs
            mi = d.book
            dummy = Metadata(_('Unknown'))
            for f in msprefs['ignore_fields']:
                if ':' not in f:
                    setattr(mi, f, getattr(dummy, f))
            if mi is not None:
                pd = mi.pubdate
                if pd is not None:
                    # Put the downloaded published date into the local timezone
                    # as we discard time info and the date is timezone
                    # invariant. This prevents the as_local_timezone() call in
                    # update_from_mi from changing the pubdate
                    mi.pubdate = datetime(pd.year, pd.month, pd.day,
                            tzinfo=local_tz)
                self.update_from_mi(mi, merge_comments=msprefs['append_comments'])
            if d.cover_pixmap is not None:
                self.metadata_before_fetch['cover'] = self.cover.current_val
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    def undo_fetch_metadata(self):
        if self.metadata_before_fetch is None:
            return error_dialog(self, _('No downloaded metadata'), _(
                'There is no downloaded metadata to undo'), show=True)
        for field, val in self.metadata_before_fetch.iteritems():
            getattr(self, field).current_val = val
        self.metadata_before_fetch = None

    def configure_metadata(self):
        from calibre.gui2.preferences import show_config_widget
        gui = self.parent()
        show_config_widget('Sharing', 'Metadata download', parent=self,
                gui=gui, never_shutdown=True)

    def download_cover(self, *args):
        from calibre.gui2.metadata.single_download import CoverFetch
        d = CoverFetch(self.cover.pixmap(), self)
        ret = d.start(self.title.current_val, self.authors.current_val,
                self.identifiers.current_val)
        if ret == d.Accepted:
            if d.cover_pixmap is not None:
                self.cover.current_val = pixmap_to_data(d.cover_pixmap)

    # }}}

    def to_book_metadata(self):
        mi = Metadata(_('Unknown'))
        if self.db is None:
            return mi
        mi.set_all_user_metadata(self.db.field_metadata.custom_field_metadata())
        for widget in self.basic_metadata_widgets:
            widget.apply_to_metadata(mi)
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.apply_to_metadata(mi)
        return mi

    def apply_changes(self):
        self.changed.add(self.book_id)
        if self.db is None:
            # break_cycles has already been called, don't know why this should
            # happen but a user reported it
            return True
        self.comments_edit_state_at_apply = {w:w.tab for w in self.comments_edit_state_at_apply}
        for widget in self.basic_metadata_widgets:
            try:
                if hasattr(widget, 'validate_for_commit'):
                    title, msg, det_msg = widget.validate_for_commit()
                    if title is not None:
                        error_dialog(self, title, msg, det_msg=det_msg, show=True)
                        return False
                widget.commit(self.db, self.book_id)
                self.books_to_refresh |= getattr(widget, 'books_to_refresh', set())
            except (IOError, OSError) as err:
                if getattr(err, 'errno', None) == errno.EACCES:  # Permission denied
                    import traceback
                    fname = getattr(err, 'filename', None)
                    p = 'Locked file: %s\n\n'%fname if fname else ''
                    error_dialog(self, _('Permission denied'),
                            _('Could not change the on disk location of this'
                                ' book. Is it open in another program?'),
                            det_msg=p+traceback.format_exc(), show=True)
                    return False
                raise
        for widget in getattr(self, 'custom_metadata_widgets', []):
            self.books_to_refresh |= widget.commit(self.book_id)

        self.db.commit()
        rows = self.db.refresh_ids(list(self.books_to_refresh))
        if rows:
            self.rows_to_refresh |= set(rows)

        return True

    def accept(self):
        self.save_state()
        if not self.apply_changes():
            return
        if self.editing_multiple and self.current_row != len(self.row_list) - 1:
            num = len(self.row_list) - 1 - self.current_row
            from calibre.gui2 import question_dialog
            if not question_dialog(
                    self, _('Are you sure?'),
                    _('There are still %d more books to edit in this set.'
                      ' Are you sure you want to stop? Use the Next button'
                      ' instead of the OK button to move through books in the set.') % num,
                    yes_text=_('&Stop editing'), no_text=_('&Continue editing'),
                    yes_icon='dot_red.png', no_icon='dot_green.png',
                    default_yes=False, skip_dialog_name='edit-metadata-single-confirm-ok-on-multiple'):
                return self.do_one(delta=1, apply_changes=False)
        ResizableDialog.accept(self)

    def reject(self):
        self.save_state()
        ResizableDialog.reject(self)

    def save_state(self):
        try:
            gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry())
        except:
            # Weird failure, see https://bugs.launchpad.net/bugs/995271
            import traceback
            traceback.print_exc()

    # Dialog use methods {{{
    def start(self, row_list, current_row, view_slot=None,
            set_current_callback=None):
        self.row_list = row_list
        self.current_row = current_row
        if view_slot is not None:
            self.view_format.connect(view_slot)
        self.set_current_callback = set_current_callback
        self.do_one(apply_changes=False)
        ret = self.exec_()
        self.break_cycles()
        return ret

    def next_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=1, apply_changes=False)

    def prev_clicked(self):
        if not self.apply_changes():
            return
        self.do_one(delta=-1, apply_changes=False)

    def do_one(self, delta=0, apply_changes=True):
        if apply_changes:
            self.apply_changes()
        self.current_row += delta
        self.update_window_title()
        prev = next_ = None
        if self.current_row > 0:
            prev = self.db.title(self.row_list[self.current_row-1])
        if self.current_row < len(self.row_list) - 1:
            next_ = self.db.title(self.row_list[self.current_row+1])

        if next_ is not None:
            tip = (_('Save changes and edit the metadata of %s')+
                    ' [Alt+Right]')%next_
            self.next_button.setToolTip(tip)
        self.next_button.setEnabled(next_ is not None)
        if prev is not None:
            tip = (_('Save changes and edit the metadata of %s')+
                    ' [Alt+Left]')%prev
            self.prev_button.setToolTip(tip)
        self.prev_button.setEnabled(prev is not None)
        self.button_box.button(self.button_box.Ok).setDefault(True)
        self.button_box.button(self.button_box.Ok).setFocus(Qt.OtherFocusReason)
        self(self.db.id(self.row_list[self.current_row]))
        for w, state in self.comments_edit_state_at_apply.iteritems():
            if state == 'code':
                w.tab = 'code'

    def break_cycles(self):
        # Break any reference cycles that could prevent python
        # from garbage collecting this dialog
        self.set_current_callback = self.db = None
        self.metadata_before_fetch = None
        def disconnect(signal):
            try:
                signal.disconnect()
            except:
                pass  # Fails if view format was never connected
        disconnect(self.view_format)
        for b in ('next_button', 'prev_button'):
            x = getattr(self, b, None)
            if x is not None:
                disconnect(x.clicked)
        for widget in self.basic_metadata_widgets:
            bc = getattr(widget, 'break_cycles', None)
            if bc is not None and callable(bc):
                bc()
        for widget in getattr(self, 'custom_metadata_widgets', []):
            widget.break_cycles()
Пример #55
0
    def create_basic_metadata_widgets(self):  # {{{
        self.basic_metadata_widgets = []

        self.languages = LanguagesEdit(self)
        self.basic_metadata_widgets.append(self.languages)

        self.title = TitleEdit(self)
        self.title.textChanged.connect(self.update_window_title)
        self.deduce_title_sort_button = QToolButton(self)
        self.deduce_title_sort_button.setToolTip(
            _('Automatically create the title sort entry based on the current '
                'title entry.\nUsing this button to create title sort will '
                'change title sort from red to green.'))
        self.deduce_title_sort_button.setWhatsThis(
                self.deduce_title_sort_button.toolTip())
        self.title_sort = TitleSortEdit(self, self.title,
                self.deduce_title_sort_button, self.languages)
        self.basic_metadata_widgets.extend([self.title, self.title_sort])

        self.deduce_author_sort_button = b = RightClickButton(self)
        b.setToolTip('<p>' +
            _('Automatically create the author sort entry based on the current '
              'author entry. Using this button to create author sort will '
              'change author sort from red to green.  There is a menu of '
              'functions available under this button. Click and hold '
              'on the button to see it.') + '</p>')
        if isosx:
            # Workaround for https://bugreports.qt-project.org/browse/QTBUG-41017
            class Menu(QMenu):

                def mouseReleaseEvent(self, ev):
                    ac = self.actionAt(ev.pos())
                    if ac is not None:
                        ac.trigger()
                    return QMenu.mouseReleaseEvent(self, ev)
            b.m = m = Menu()
        else:
            b.m = m = QMenu()
        ac = m.addAction(QIcon(I('forward.png')), _('Set author sort from author'))
        ac2 = m.addAction(QIcon(I('back.png')), _('Set author from author sort'))
        ac3 = m.addAction(QIcon(I('user_profile.png')), _('Manage authors'))
        ac4 = m.addAction(QIcon(I('next.png')),
                _('Copy author to author sort'))
        ac5 = m.addAction(QIcon(I('previous.png')),
                _('Copy author sort to author'))

        b.setMenu(m)
        self.authors = AuthorsEdit(self, ac3)
        self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac,
                ac2, ac4, ac5)
        self.basic_metadata_widgets.extend([self.authors, self.author_sort])

        self.swap_title_author_button = QToolButton(self)
        self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
        self.swap_title_author_button.setToolTip(_(
            'Swap the author and title') + ' [%s]' % self.swap_title_author_shortcut.key().toString(QKeySequence.NativeText))
        self.swap_title_author_button.clicked.connect(self.swap_title_author)
        self.swap_title_author_shortcut.activated.connect(self.swap_title_author_button.click)

        self.manage_authors_button = QToolButton(self)
        self.manage_authors_button.setIcon(QIcon(I('user_profile.png')))
        self.manage_authors_button.setToolTip('<p>' + _(
            'Manage authors. Use to rename authors and correct '
            'individual author\'s sort values') + '</p>')
        self.manage_authors_button.clicked.connect(self.authors.manage_authors)

        self.series = SeriesEdit(self)
        self.clear_series_button = QToolButton(self)
        self.clear_series_button.setToolTip(
               _('Clear series'))
        self.clear_series_button.clicked.connect(self.series.clear)
        self.series_index = SeriesIndexEdit(self, self.series)
        self.basic_metadata_widgets.extend([self.series, self.series_index])

        self.formats_manager = FormatsManager(self, self.copy_fmt)
        # We want formats changes to be committed before title/author, as
        # otherwise we could have data loss if the title/author changed and the
        # user was trying to add an extra file from the old books directory.
        self.basic_metadata_widgets.insert(0, self.formats_manager)
        self.formats_manager.metadata_from_format_button.clicked.connect(
                self.metadata_from_format)
        self.formats_manager.cover_from_format_button.clicked.connect(
                self.cover_from_format)
        self.cover = Cover(self)
        self.cover.download_cover.connect(self.download_cover)
        self.basic_metadata_widgets.append(self.cover)

        self.comments = CommentsEdit(self, self.one_line_comments_toolbar)
        self.basic_metadata_widgets.append(self.comments)

        self.rating = RatingEdit(self)
        self.clear_ratings_button = QToolButton(self)
        self.clear_ratings_button.setToolTip(_('Clear rating'))
        self.clear_ratings_button.setIcon(QIcon(I('trash.png')))
        self.clear_ratings_button.clicked.connect(self.rating.zero)

        self.basic_metadata_widgets.append(self.rating)

        self.tags = TagsEdit(self)
        self.tags_editor_button = QToolButton(self)
        self.tags_editor_button.setToolTip(_('Open Tag Editor'))
        self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
        self.tags_editor_button.clicked.connect(self.tags_editor)
        self.clear_tags_button = QToolButton(self)
        self.clear_tags_button.setToolTip(_('Clear all tags'))
        self.clear_tags_button.setIcon(QIcon(I('trash.png')))
        self.clear_tags_button.clicked.connect(self.tags.clear)
        self.basic_metadata_widgets.append(self.tags)

        self.identifiers = IdentifiersEdit(self)
        self.basic_metadata_widgets.append(self.identifiers)
        self.clear_identifiers_button = QToolButton(self)
        self.clear_identifiers_button.setIcon(QIcon(I('trash.png')))
        self.clear_identifiers_button.setToolTip(_('Clear Ids'))
        self.clear_identifiers_button.clicked.connect(self.identifiers.clear)
        self.paste_isbn_button = QToolButton(self)
        self.paste_isbn_button.setToolTip('<p>' +
                    _('Paste the contents of the clipboard into the '
                      'identifiers box prefixed with isbn:') + '</p>')
        self.paste_isbn_button.setIcon(QIcon(I('edit-paste.png')))
        self.paste_isbn_button.clicked.connect(self.identifiers.paste_isbn)

        self.publisher = PublisherEdit(self)
        self.basic_metadata_widgets.append(self.publisher)

        self.timestamp = DateEdit(self)
        self.pubdate = PubdateEdit(self)
        self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])

        self.fetch_metadata_button = b = RightClickButton(self)
        # The following rigmarole is needed so that Qt gives the button the
        # same height as the other buttons in the dialog. There is no way to
        # center the text in a QToolButton with an icon, so we cant just set an
        # icon
        b.setIcon(QIcon(I('download-metadata.png')))
        b.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        b.setMinimumHeight(b.sizeHint().height())
        b.setIcon(QIcon())
        b.setText(_('&Download metadata')), b.setPopupMode(b.DelayedPopup)
        b.setToolTip(_('Download metadata for this book [%s]') % self.download_shortcut.key().toString(QKeySequence.NativeText))
        b.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
        self.fetch_metadata_menu = m = QMenu(self.fetch_metadata_button)
        m.addAction(QIcon(I('edit-undo.png')), _('Undo last metadata download'), self.undo_fetch_metadata)
        self.fetch_metadata_button.setMenu(m)
        self.download_shortcut.activated.connect(self.fetch_metadata_button.click)
        font = self.fmb_font = QFont()
        font.setBold(True)
        self.fetch_metadata_button.setFont(font)

        if self.use_toolbutton_for_config_metadata:
            self.config_metadata_button = QToolButton(self)
            self.config_metadata_button.setIcon(QIcon(I('config.png')))
        else:
            self.config_metadata_button = QPushButton(self)
            self.config_metadata_button.setText(_('Configure download metadata'))
        self.config_metadata_button.setIcon(QIcon(I('config.png')))
        self.config_metadata_button.clicked.connect(self.configure_metadata)
        self.config_metadata_button.setToolTip(
            _('Change how calibre downloads metadata'))
    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle('Find Annotations')
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle("Search criteria")
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel('Reader')
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for')

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel('Style')
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip('Annotation style to search for')

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel('Text')
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip('Clear search criteria')
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel('Note')
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip('Clear search criteria')
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel('Date range')
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">scanning…</p>')
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton('Update results')
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText('Find Matching Books')

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)
class FindAnnotationsDialog(SizePersistedDialog, Logger):

    GENERIC_STYLE = 'Any style'
    GENERIC_READER = 'Any reader'

    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle('Find Annotations')
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle("Search criteria")
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel('Reader')
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for')

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel('Style')
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip('Annotation style to search for')

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel('Text')
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip('Clear search criteria')
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel('Note')
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip('Clear search criteria')
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel('Date range')
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">scanning…</p>')
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton('Update results')
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText('Find Matching Books')

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)

    def clear_note_field(self):
        if str(self.find_annotations_note_lineEdit.text()) > '':
            self.find_annotations_note_lineEdit.setText('')
            self.update_results('clear_note_field')

    def clear_text_field(self):
        if str(self.find_annotations_text_lineEdit.text()) > '':
            self.find_annotations_text_lineEdit.setText('')
            self.update_results('clear_text_field')

    def find_annotations_dialog_clicked(self, button):
        if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self.save_settings()
            self.accept()
        elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def inventory_available(self):
        '''
        Update the Date range widgets with the rounded oldest, newest dates
        Don't connect date signals until date range available
        '''
        self._log_location()

        # Reset the date range based on available annotations
        oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation))
        oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0))
        newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation))
        newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59))

        # Set 'From' date limits to inventory values
        self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_from_dateEdit.current_val = oldest
        self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day)

        # Set 'To' date limits to inventory values
        self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_to_dateEdit.current_val = newest_day
        self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day)

        # Connect the signals for date range changes
        self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date'))
        self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date'))

        self.update_results('inventory_available')

    def restore_settings(self):
        self.blockSignals(True)

        ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER)
        ra_index = self.find_annotations_reader_comboBox.findText(ra)
        self.find_annotations_reader_comboBox.setCurrentIndex(ra_index)

        color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE)
        color_index = self.find_annotations_color_comboBox.findText(color)
        self.find_annotations_color_comboBox.setCurrentIndex(color_index)

        text = self.prefs.get('find_annotations_text_lineEdit', '')
        self.find_annotations_text_lineEdit.setText(text)

        note = self.prefs.get('find_annotations_note_lineEdit', '')
        self.find_annotations_note_lineEdit.setText(note)

        if False:
            from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1))
            self.find_annotations_date_from_dateEdit.current_val = from_date
            to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today())
            self.find_annotations_date_to_dateEdit.current_val = to_date

        self.blockSignals(False)

    def return_pressed(self):
        self.update_results("return_pressed")

    def save_settings(self):
        ra = str(self.find_annotations_reader_comboBox.currentText())
        self.prefs.set('find_annotations_reader_comboBox', ra)

        color = str(self.find_annotations_color_comboBox.currentText())
        self.prefs.set('find_annotations_color_comboBox', color)

        text = str(self.find_annotations_text_lineEdit.text())
        self.prefs.set('find_annotations_text_lineEdit', text)

        note = str(self.find_annotations_note_lineEdit.text())
        self.prefs.set('find_annotations_note_lineEdit', note)

        if False:
            from_date = self.find_annotations_date_from_dateEdit.current_val
            self.prefs.set('find_annotations_date_from_dateEdit', from_date)

            to_date = self.find_annotations_date_to_dateEdit.current_val
            self.prefs.set('find_annotations_date_to_dateEdit', to_date)

    def start_inventory_scan(self):
        self._log_location()
        self.annotated_books_scanner.start()

    def update_results(self, trigger):
        #self._log_location(trigger)
        reader_to_match = str(self.find_annotations_reader_comboBox.currentText())
        color_to_match = str(self.find_annotations_color_comboBox.currentText())
        text_to_match = str(self.find_annotations_text_lineEdit.text())
        note_to_match = str(self.find_annotations_note_lineEdit.text())

        from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t()
        to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t()

        annotation_map = self.annotated_books_scanner.annotation_map
        #field = self.prefs.get("cfg_annotations_destination_field", None)
        field = get_cc_mapping('annotations', 'field', None)

        db = self.opts.gui.current_db
        matched_titles = []
        self.matched_ids = set()

        for cid in annotation_map:
            mi = db.get_metadata(cid, index_is_id=True)
            soup = None
            if field == 'Comments':
                if mi.comments:
                    soup = BeautifulSoup(mi.comments)
            else:
                if mi.get_user_metadata(field, False)['#value#'] is not None:
                    soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#'])
            if soup:
                uas = soup.findAll('div', 'annotation')
                for ua in uas:
                    # Are we already logged?
                    if cid in self.matched_ids:
                        continue

                    # Check reader
                    if reader_to_match != self.GENERIC_READER:
                        this_reader = ua['reader']
                        if this_reader != reader_to_match:
                            continue

                    # Check color
                    if color_to_match != self.GENERIC_STYLE:
                        this_color = ua.find('table')['color']
                        if this_color != color_to_match:
                            continue

                    # Check date range, allow for mangled timestamp
                    try:
                        timestamp = float(ua.find('td', 'timestamp')['uts'])
                        if timestamp < from_date or timestamp > to_date:
                            continue
                    except:
                        continue

                    highlight_text = ''
                    try:
                        pels = ua.findAll('p', 'highlight')
                        for pel in pels:
                            highlight_text += pel.string + '\n'
                    except:
                        pass
                    if text_to_match > '':
                        if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE):
                            continue

                    note_text = ''
                    try:
                        nels = ua.findAll('p', 'note')
                        for nel in nels:
                            note_text += nel.string + '\n'
                    except:
                        pass
                    if note_to_match > '':
                        if not re.search(note_to_match, note_text, flags=re.IGNORECASE):
                            continue

                    # If we made it this far, add the id to matched_ids
                    self.matched_ids.add(cid)
                    matched_titles.append(mi.title)

        # Update the results box
        matched_titles.sort()
        if len(annotation_map):
            if len(matched_titles):
                first_match = ("<i>%s</i>" % matched_titles[0])
                if len(matched_titles) == 1:
                    results = first_match
                else:
                    results = first_match + (" and %d more." % (len(matched_titles) - 1))
                self.result_label.setText('<p style="color:blue">{0}</p>'.format(results))
            else:
                self.result_label.setText('<p style="color:red">no matches</p>')
        else:
            self.result_label.setText('<p style="color:red">no annotated books in library</p>')

        self.resize_dialog()