Esempio n. 1
0
class RichTextDelegate(QStyledItemDelegate):  # {{{
    def __init__(self, parent=None, max_width=160):
        QStyledItemDelegate.__init__(self, parent)
        self.max_width = max_width
        self.dummy_model = QStringListModel([' '], self)
        self.dummy_index = self.dummy_model.index(0)

    def to_doc(self, index, option=None):
        doc = QTextDocument()
        if option is not None and option.state & QStyle.StateFlag.State_Selected:
            p = option.palette
            group = (QPalette.ColorGroup.Active if option.state
                     & QStyle.StateFlag.State_Active else
                     QPalette.ColorGroup.Inactive)
            c = p.color(group, QPalette.ColorRole.HighlightedText)
            c = 'rgb(%d, %d, %d)' % c.getRgb()[:3]
            doc.setDefaultStyleSheet(' * { color: %s }' % c)
        doc.setHtml(index.data() or '')
        return doc

    def sizeHint(self, option, index):
        doc = self.to_doc(index, option=option)
        ans = doc.size().toSize()
        if ans.width() > self.max_width - 10:
            ans.setWidth(self.max_width)
        ans.setHeight(ans.height() + 10)
        return ans

    def paint(self, painter, option, index):
        QStyledItemDelegate.paint(self, painter, option, self.dummy_index)
        painter.save()
        painter.setClipRect(QRectF(option.rect))
        painter.translate(option.rect.topLeft())
        self.to_doc(index, option).drawContents(painter)
        painter.restore()
Esempio n. 2
0
class TagEditor(QDialog, Ui_TagEditor):
    def __init__(self, window, db, id_=None, key=None, current_tags=None):
        QDialog.__init__(self, window)
        Ui_TagEditor.__init__(self)
        self.setupUi(self)

        self.db = db
        self.sep = ','
        self.is_names = False
        if key:
            # Assume that if given a key then it is a custom column
            try:
                fm = db.field_metadata[key]
                self.is_names = fm['display'].get('is_names', False)
                if self.is_names:
                    self.sep = '&'
                self.setWindowTitle(self.windowTitle() + ': ' + fm['name'])
            except Exception:
                pass
            key = db.field_metadata.key_to_label(key)
        else:
            self.setWindowTitle(self.windowTitle() + ': ' +
                                db.field_metadata['tags']['name'])

        if self.sep == '&':
            self.add_tag_input.setToolTip(
                '<p>' +
                _('If the item you want is not in the available list, '
                  'you can add it here. Accepts an ampersand-separated '
                  'list of items. The items will be applied to '
                  'the book.') + '</p>')
        else:
            self.add_tag_input.setToolTip(
                '<p>' + _('If the item you want is not in the available list, '
                          'you can add it here. Accepts a comma-separated '
                          'list of items. The items will be applied to '
                          'the book.') + '</p>')
        self.key = key
        self.index = db.row(id_) if id_ is not None else None
        if self.index is not None:
            if key is None:
                tags = self.db.tags(self.index)
                if tags:
                    tags = [
                        tag.strip() for tag in tags.split(',') if tag.strip()
                    ]
            else:
                tags = self.db.get_custom(self.index, label=key)
        else:
            tags = []
        if current_tags is not None:
            tags = sorted(set(current_tags), key=sort_key)
        if tags:
            if not self.is_names:
                tags.sort(key=sort_key)
        else:
            tags = []
        self.applied_model = QStringListModel(tags)
        p = QSortFilterProxyModel()
        p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
        p.setSourceModel(self.applied_model)
        self.applied_tags.setModel(p)
        if self.is_names:
            self.applied_tags.setDragDropMode(
                QAbstractItemView.DragDropMode.InternalMove)
            self.applied_tags.setSelectionMode(
                QAbstractItemView.SelectionMode.ExtendedSelection)

        if key:
            all_tags = [tag for tag in self.db.all_custom(label=key)]
        else:
            all_tags = [tag for tag in self.db.all_tags()]
        all_tags = sorted(set(all_tags) - set(tags), key=sort_key)
        self.all_tags_model = QStringListModel(all_tags)
        p = QSortFilterProxyModel()
        p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
        p.setSourceModel(self.all_tags_model)
        self.available_tags.setModel(p)

        connect_lambda(self.apply_button.clicked, self,
                       lambda self: self.apply_tags())
        connect_lambda(self.unapply_button.clicked, self,
                       lambda self: self.unapply_tags())
        self.add_tag_button.clicked.connect(self.add_tag)
        connect_lambda(self.delete_button.clicked, self,
                       lambda self: self.delete_tags())
        self.add_tag_input.returnPressed[()].connect(self.add_tag)
        # add the handlers for the filter input fields
        connect_lambda(self.available_filter_input.textChanged, self,
                       lambda self, text: self.filter_tags(text))
        connect_lambda(
            self.applied_filter_input.textChanged, self,
            lambda self, text: self.filter_tags(text, which='applied_tags'))

        # Restore the focus to the last input box used (typed into)
        for x in ('add_tag_input', 'available_filter_input',
                  'applied_filter_input'):
            ibox = getattr(self, x)
            ibox.setObjectName(x)
            connect_lambda(
                ibox.textChanged, self,
                lambda self: self.edit_box_changed(self.sender().objectName()))
        getattr(self, gprefs.get('tag_editor_last_filter',
                                 'add_tag_input')).setFocus()

        self.available_tags.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)
        self.applied_tags.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)
        if islinux:
            self.available_tags.doubleClicked.connect(self.apply_tags)
            self.applied_tags.doubleClicked.connect(self.unapply_tags)
        else:
            self.available_tags.activated.connect(self.apply_tags)
            self.applied_tags.activated.connect(self.unapply_tags)

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

    def edit_box_changed(self, which):
        gprefs['tag_editor_last_filter'] = which

    def delete_tags(self):
        confirms, deletes = [], []
        row_indices = list(self.available_tags.selectionModel().selectedRows())

        if not row_indices:
            error_dialog(
                self, 'No tags selected',
                'You must select at least one tag from the list of Available tags.'
            ).exec()
            return
        if not confirm(
                _('Deleting tags is done immediately and there is no undo.'),
                'tag_editor_delete'):
            return
        pos = self.available_tags.verticalScrollBar().value()
        for ri in row_indices:
            tag = ri.data()
            used = self.db.is_tag_used(tag) \
                if self.key is None else \
                self.db.is_item_used_in_multiple(tag, label=self.key)
            if used:
                confirms.append(ri)
            else:
                deletes.append(ri)
        if confirms:
            ct = ', '.join(item.data() for item in confirms)
            if question_dialog(
                    self, _('Are your sure?'), '<p>' +
                    _('The following tags are used by one or more books. '
                      'Are you certain you want to delete them?') + '<br>' +
                    ct):
                deletes += confirms

        for item in sorted(deletes, key=lambda r: r.row(), reverse=True):
            tag = item.data()
            if self.key is None:
                self.db.delete_tag(tag)
            else:
                bks = self.db.delete_item_from_multiple(tag, label=self.key)
                self.db.refresh_ids(bks)
            self.available_tags.model().removeRows(item.row(), 1)
        self.available_tags.verticalScrollBar().setValue(pos)

    def apply_tags(self, item=None):
        row_indices = list(self.available_tags.selectionModel().selectedRows())
        row_indices.sort(key=lambda r: r.row(), reverse=True)
        if not row_indices:
            text = self.available_filter_input.text()
            if text and text.strip():
                self.add_tag_input.setText(text)
                self.add_tag_input.setFocus(Qt.FocusReason.OtherFocusReason)
            return
        pos = self.available_tags.verticalScrollBar().value()
        tags = self._get_applied_tags_box_contents()
        for item in row_indices:
            tag = item.data()
            tags.append(tag)
            self.available_tags.model().removeRows(item.row(), 1)
        self.available_tags.verticalScrollBar().setValue(pos)

        if not self.is_names:
            tags.sort(key=sort_key)
        self.applied_model.setStringList(tags)

    def _get_applied_tags_box_contents(self):
        return list(self.applied_model.stringList())

    def unapply_tags(self, item=None):
        row_indices = list(self.applied_tags.selectionModel().selectedRows())
        tags = [r.data() for r in row_indices]
        row_indices.sort(key=lambda r: r.row(), reverse=True)
        for item in row_indices:
            self.applied_model.removeRows(item.row(), 1)

        all_tags = self.all_tags_model.stringList() + tags
        all_tags.sort(key=sort_key)
        self.all_tags_model.setStringList(all_tags)

    def add_tag(self):
        tags = str(self.add_tag_input.text()).split(self.sep)
        tags_in_box = self._get_applied_tags_box_contents()
        for tag in tags:
            tag = tag.strip()
            if not tag:
                continue
            if self.all_tags_model.rowCount():
                for index in self.all_tags_model.match(
                        self.all_tags_model.index(0),
                        Qt.ItemDataRole.DisplayRole, tag, -1,
                        Qt.MatchFlag.MatchFixedString
                        | Qt.MatchFlag.MatchCaseSensitive
                        | Qt.MatchFlag.MatchWrap):
                    self.all_tags_model.removeRow(index.row())
            if tag not in tags_in_box:
                tags_in_box.append(tag)

        if not self.is_names:
            tags_in_box.sort(key=sort_key)
        self.applied_model.setStringList(tags_in_box)
        self.add_tag_input.setText('')

    def filter_tags(self, filter_value, which='available_tags'):
        collection = getattr(self, which)
        collection.model().setFilterFixedString(filter_value or '')

    def accept(self):
        self.tags = self._get_applied_tags_box_contents()
        self.save_state()
        return QDialog.accept(self)

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

    def save_state(self):
        gprefs['tag_editor_geometry'] = bytearray(self.saveGeometry())
Esempio n. 3
0
class FontFamilyDialog(QDialog):
    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.ConnectionType.QueuedConnection)
        self.view.changed.connect(self.current_changed,
                                  type=Qt.ConnectionType.QueuedConnection)
        self.faces = Typefaces(self)
        self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok
                                   | QDialogButtonBox.StandardButton.Cancel)
        self.bb.accepted.connect(self.accept)
        self.bb.rejected.connect(self.reject)
        self.add_fonts_button = afb = self.bb.addButton(
            _('Add &fonts'), QDialogButtonBox.ButtonRole.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.AlignmentFlag.AlignTop)

        self.resize(800, 600)

    def set_current(self, i):
        self.view.setCurrentIndex(self.m.index(i))

    def keyPressEvent(self, e):
        if e.key() == Qt.Key.Key_Return:
            return
        return QDialog.keyPressEvent(self, e)

    def find(self, backwards=False):
        i = self.view.currentIndex().row()
        if i < 0:
            i = 0
        q = icu_lower(unicode_type(self.search.text())).strip()
        if not q:
            return
        r = (range(i -
                   1, -1, -1) if backwards else range(i +
                                                      1, len(self.families)))
        for j in r:
            f = self.families[j]
            if q in icu_lower(f):
                self.set_current(j)
                return

    def find_next(self):
        self.find()

    def find_previous(self):
        self.find(backwards=True)

    def build_font_list(self):
        try:
            self.families = list(self.font_scanner.find_font_families())
        except:
            self.families = []
            print('WARNING: Could not load fonts')
            import traceback
            traceback.print_exc()
        self.families.insert(0, _('None'))
        self.m.setStringList(self.families)

    def add_fonts(self):
        families = add_fonts(self)
        if not families:
            return
        self.font_scanner.do_scan()
        self.m.beginResetModel()
        self.build_font_list()
        self.m.endResetModel()
        self.view.setCurrentIndex(self.m.index(0))
        if families:
            for i, val in enumerate(self.families):
                if icu_lower(val) == icu_lower(families[0]):
                    self.view.setCurrentIndex(self.m.index(i))
                    break

        info_dialog(self,
                    _('Added fonts'),
                    _('Added font families: %s') % (', '.join(families)),
                    show=True)

    @property
    def font_family(self):
        idx = self.view.currentIndex().row()
        if idx == 0:
            return None
        return self.families[idx]

    def current_changed(self):
        fam = self.font_family
        self.faces.show_family(
            fam,
            self.font_scanner.fonts_for_family(fam) if fam else None)