def createEditor(self, parent, option, index):
     if self.db and hasattr(self.db, self.items_func_name):
         col = self.col
         if col is None:
             # We have not specified an explicit column, so we need
             # to lookup a column name. The calibre one will rely on stuff
             # on the model, we will rely on a callback function instead
             col = self.col_fn(index.column())
         editor = EditWithComplete(parent)
         editor.set_separator(self.sep)
         editor.set_space_before_sep(self.space_before_sep)
         if self.sep == '&':
             editor.set_add_separator(
                 tweaks['authors_completer_append_separator'])
         if col.startswith('#'):
             all_items = list(
                 self.db.all_custom(
                     label=self.db.field_metadata.key_to_label(col)))
         else:
             all_items = getattr(self.db, self.items_func_name)()
         editor.update_items_cache(all_items)
         for item in sorted(all_items, key=sort_key):
             editor.addItem(item)
         ct = index.data(Qt.DisplayRole).toString()
         editor.show_initial_value(ct)
     else:
         editor = EnLineEdit(parent)
     return editor
 def createEditor(self, parent, option, index):
     if self.db and hasattr(self.db, self.items_func_name):
         col = self.col
         if col is None:
             # We have not specified an explicit column, so we need
             # to lookup a column name. The calibre one will rely on stuff
             # on the model, we will rely on a callback function instead
             col = self.col_fn(index.column())
         editor = EditWithComplete(parent)
         editor.set_separator(self.sep)
         editor.set_space_before_sep(self.space_before_sep)
         if self.sep == '&':
             editor.set_add_separator(tweaks['authors_completer_append_separator'])
         if col.startswith('#'):
             all_items = list(self.db.all_custom(
                 label=self.db.field_metadata.key_to_label(col)))
         else:
             all_items = getattr(self.db, self.items_func_name)()
         editor.update_items_cache(all_items)
         for item in sorted(all_items, key=sort_key):
             editor.addItem(item)
         ct = index.data(Qt.DisplayRole).toString()
         editor.show_initial_value(ct)
     else:
         editor = EnLineEdit(parent)
     return editor
Beispiel #3
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()
Beispiel #4
0
 def createEditor(self, parent, option, index):
     if self.db and hasattr(self.db, self.items_func_name):
         editor = EditWithComplete(parent)
         editor.set_separator(self.sep)
         editor.set_space_before_sep(self.space_before_sep)
         if self.sep == '&':
             editor.set_add_separator(
                 tweaks['authors_completer_append_separator'])
         if self.col.startswith('#'):
             all_items = list(
                 self.db.all_custom(
                     label=self.db.field_metadata.key_to_label(self.col)))
         else:
             all_items = getattr(self.db, self.items_func_name)()
         editor.update_items_cache(all_items)
         for item in sorted(all_items, key=sort_key):
             editor.addItem(item)
         ct = index.data(Qt.DisplayRole).toString()
         editor.show_initial_value(ct)
     else:
         editor = EnLineEdit(parent)
     return editor
class SeriesDialog(SizePersistedDialog):
    def __init__(self, parent, books, all_series, series_columns):
        SizePersistedDialog.__init__(self, parent,
                                     'Manage Series plugin:series dialog')
        self.db = self.parent().library_view.model().db
        self.books = books
        self.all_series = all_series
        self.series_columns = series_columns
        self.block_events = True

        self.initialize_controls()

        # Books will have been sorted by the Calibre series column
        # Choose the appropriate series column to be editing
        initial_series_column = 'Series'
        self.series_column_combo.select_text(initial_series_column)
        if len(series_columns) == 0:
            # Will not have fired the series_column_changed event
            self.series_column_changed()
        # Renumber the books using the assigned series name/index in combos/spinbox
        self.renumber_series(display_in_table=False)

        # Display the books in the table
        self.block_events = False
        self.series_table.populate_table(books)
        if len(unicode(self.series_combo.text()).strip()) > 0:
            self.series_table.setFocus()
        else:
            self.series_combo.setFocus()
        self.update_series_headers(initial_series_column)

        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()

    def initialize_controls(self):
        self.setWindowTitle('Manage Series')
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        title_layout = ImageTitleLayout(self, 'images/manage_series.png',
                                        'Create or Modify Series')
        layout.addLayout(title_layout)

        # Series name and start index layout
        series_name_layout = QHBoxLayout()
        layout.addLayout(series_name_layout)

        series_column_label = QLabel('Series &Column:', self)
        series_name_layout.addWidget(series_column_label)
        self.series_column_combo = SeriesColumnComboBox(
            self, self.series_columns)
        self.series_column_combo.currentIndexChanged[int].connect(
            self.series_column_changed)
        series_name_layout.addWidget(self.series_column_combo)
        series_column_label.setBuddy(self.series_column_combo)
        series_name_layout.addSpacing(20)

        series_label = QLabel('Series &Name:', self)
        series_name_layout.addWidget(series_label)
        self.series_combo = EditWithComplete(self)
        self.series_combo.setEditable(True)
        self.series_combo.setInsertPolicy(QtGui.QComboBox.InsertAlphabetically)
        self.series_combo.setSizeAdjustPolicy(
            QtGui.QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.series_combo.setMinimumContentsLength(25)
        self.series_combo.currentIndexChanged[int].connect(self.series_changed)
        self.series_combo.editTextChanged.connect(self.series_changed)
        self.series_combo.set_separator(None)
        series_label.setBuddy(self.series_combo)
        series_name_layout.addWidget(self.series_combo)
        series_name_layout.addSpacing(20)
        series_start_label = QLabel('&Start At:', self)
        series_name_layout.addWidget(series_start_label)
        self.series_start_number = QtGui.QSpinBox(self)
        self.series_start_number.setRange(0, 99000000)
        self.series_start_number.valueChanged[int].connect(
            self.series_start_changed)
        series_name_layout.addWidget(self.series_start_number)
        series_start_label.setBuddy(self.series_start_number)
        series_name_layout.insertStretch(-1)

        # Main series table layout
        table_layout = QHBoxLayout()
        layout.addLayout(table_layout)

        self.series_table = SeriesTableWidget(self)
        self.series_table.itemSelectionChanged.connect(
            self.item_selection_changed)
        self.series_table.cellChanged[int, int].connect(self.cell_changed)

        table_layout.addWidget(self.series_table)
        table_button_layout = QVBoxLayout()
        table_layout.addLayout(table_button_layout)
        move_up_button = QtGui.QToolButton(self)
        move_up_button.setToolTip('Move book up in series (Alt+Up)')
        move_up_button.setIcon(get_icon('arrow-up.png'))
        move_up_button.setShortcut(_('Alt+Up'))
        move_up_button.clicked.connect(self.move_rows_up)
        table_button_layout.addWidget(move_up_button)
        move_down_button = QtGui.QToolButton(self)
        move_down_button.setToolTip('Move book down in series (Alt+Down)')
        move_down_button.setIcon(get_icon('arrow-down.png'))
        move_down_button.setShortcut(_('Alt+Down'))
        move_down_button.clicked.connect(self.move_rows_down)
        table_button_layout.addWidget(move_down_button)
        spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        table_button_layout.addItem(spacerItem1)
        assign_index_button = QtGui.QToolButton(self)
        assign_index_button.setToolTip('Lock to index value...')
        assign_index_button.setIcon(get_icon('images/lock.png'))
        assign_index_button.clicked.connect(self.assign_index)
        table_button_layout.addWidget(assign_index_button)
        clear_index_button = QtGui.QToolButton(self)
        clear_index_button.setToolTip('Unlock series index')
        clear_index_button.setIcon(get_icon('images/lock_delete.png'))
        clear_index_button.clicked.connect(self.clear_index)
        table_button_layout.addWidget(clear_index_button)
        spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        table_button_layout.addItem(spacerItem2)
        add_empty_button = QtGui.QToolButton(self)
        add_empty_button.setToolTip('Add empty book to the series list')
        add_empty_button.setIcon(get_icon('add_book.png'))
        add_empty_button.setShortcut(_('Ctrl+Shift+E'))
        add_empty_button.clicked.connect(self.add_empty_book)
        table_button_layout.addWidget(add_empty_button)
        delete_button = QtGui.QToolButton(self)
        delete_button.setToolTip('Remove book from the series list')
        delete_button.setIcon(get_icon('trash.png'))
        delete_button.clicked.connect(self.remove_book)
        table_button_layout.addWidget(delete_button)
        spacerItem3 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum,
                                        QtGui.QSizePolicy.Expanding)
        table_button_layout.addItem(spacerItem3)
        move_left_button = QtGui.QToolButton(self)
        move_left_button.setToolTip(
            'Move series index to left of decimal point (Alt+Left)')
        move_left_button.setIcon(get_icon('back.png'))
        move_left_button.setShortcut(_('Alt+Left'))
        move_left_button.clicked.connect(partial(self.series_indent_change,
                                                 -1))
        table_button_layout.addWidget(move_left_button)
        move_right_button = QtGui.QToolButton(self)
        move_right_button.setToolTip(
            'Move series index to right of decimal point (Alt+Right)')
        move_right_button.setIcon(get_icon('forward.png'))
        move_right_button.setShortcut(_('Alt+Right'))
        move_right_button.clicked.connect(partial(self.series_indent_change,
                                                  1))
        table_button_layout.addWidget(move_right_button)

        # Dialog buttons
        button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                      | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)
        keep_button = button_box.addButton(' &Restore Original Series ',
                                           QDialogButtonBox.ResetRole)
        keep_button.clicked.connect(self.restore_original_series)

    def series_column_changed(self):
        series_column = self.series_column_combo.selected_value()
        SeriesBook.series_column = series_column
        # Choose a series name and series index from the first book in the list
        initial_series_name = ''
        initial_series_index = 1
        if len(self.books) > 0:
            first_book = self.books[0]
            initial_series_name = first_book.series_name()
            if initial_series_name:
                initial_series_index = int(first_book.series_index())
        # Populate the series name combo as appropriate for that column
        self.initialize_series_name_combo(series_column, initial_series_name)
        # Populate the series index spinbox with the initial value
        self.series_start_number.setProperty('value', initial_series_index)
        self.update_series_headers(series_column)
        if self.block_events:
            return
        self.renumber_series()

    def update_series_headers(self, series_column):
        if series_column == 'Series':
            self.series_table.set_series_column_headers(series_column)
        else:
            header_text = self.series_columns[series_column]['name']
            self.series_table.set_series_column_headers(header_text)

    def initialize_series_name_combo(self, series_column, series_name):
        self.series_combo.clear()
        if series_name is None:
            series_name = ''
        values = self.all_series
        if series_column == 'Series':
            self.series_combo.update_items_cache([x[1] for x in values])
            for i in values:
                _id, name = i
                self.series_combo.addItem(name)
        else:
            label = self.db.field_metadata.key_to_label(series_column)
            values = list(self.db.all_custom(label=label))
            values.sort(key=sort_key)
            self.series_combo.update_items_cache(values)
            for name in values:
                self.series_combo.addItem(name)
        self.series_combo.setEditText(series_name)

    def series_changed(self):
        if self.block_events:
            return
        self.renumber_series()

    def series_start_changed(self):
        if self.block_events:
            return
        self.renumber_series()

    def restore_original_series(self):
        # Go through the books and overwrite the indexes with the originals, fixing in place
        for book in self.books:
            if book.orig_series_index():
                book.set_assigned_index(book.orig_series_index())
                #book.set_series_name(book.orig_series_name())
                book.set_series_index(book.orig_series_index())
        # Now renumber the whole series so that anything in between gets changed
        self.renumber_series()

    def renumber_series(self, display_in_table=True):
        if len(self.books) == 0:
            return
        series_name = unicode(self.series_combo.currentText()).strip()
        series_index = float(unicode(self.series_start_number.value()))
        last_series_indent = 0
        for row, book in enumerate(self.books):
            book.set_series_name(series_name)
            series_indent = book.series_indent()
            if book.assigned_index() is not None:
                series_index = book.assigned_index()
            else:
                if series_indent >= last_series_indent:
                    if series_indent == 0:
                        if row > 0:
                            series_index += 1.
                    elif series_indent == 1:
                        series_index += 0.1
                    else:
                        series_index += 0.01
                else:
                    # When series indent decreases, need to round to next
                    if series_indent == 1:
                        series_index = round(series_index + 0.05, 1)
                    else:  # series_indent == 0:
                        series_index = round(series_index + 0.5, 0)
            book.set_series_index(series_index)
            last_series_indent = series_indent
        # Now determine whether books have a valid index or not
        self.books[0].set_is_valid(True)
        for row in range(len(self.books) - 1, 0, -1):
            book = self.books[row]
            previous_book = self.books[row - 1]
            if book.series_index() <= previous_book.series_index():
                book.set_is_valid(False)
            else:
                book.set_is_valid(True)
        if display_in_table:
            for row, book in enumerate(self.books):
                self.series_table.populate_table_row(row, book)

    def assign_original_index(self):
        if len(self.books) == 0:
            return
        for row in self.series_table.selectionModel().selectedRows():
            book = self.books[row.row()]
            book.set_assigned_index(book.orig_series_index())
        self.renumber_series()
        self.item_selection_changed()

    def assign_index(self):
        if len(self.books) == 0:
            return
        auto_assign_value = None
        for row in self.series_table.selectionModel().selectedRows():
            book = self.books[row.row()]
            if auto_assign_value is not None:
                book.set_assigned_index(auto_assign_value)
                continue

            d = LockSeriesDialog(self, book.title(), book.series_index())
            d.exec_()
            if d.result() != d.Accepted:
                break
            if d.assign_same_value():
                auto_assign_value = d.get_value()
                book.set_assigned_index(auto_assign_value)
            else:
                book.set_assigned_index(d.get_value())

        self.renumber_series()
        self.item_selection_changed()

    def clear_index(self, all_rows=False):
        if len(self.books) == 0:
            return
        if all_rows:
            for book in self.books:
                book.set_assigned_index(None)
        else:
            for row in self.series_table.selectionModel().selectedRows():
                book = self.books[row.row()]
                book.set_assigned_index(None)
        self.renumber_series()

    def add_empty_book(self):
        def create_empty_book(authors):
            mi = Metadata(_('Unknown'), dlg.selected_authors)
            for key in self.series_columns.keys():
                meta = self.db.metadata_for_field(key)
                mi.set_user_metadata(key, meta)
                mi.set(key, val=None, extra=None)
            return SeriesBook(mi, self.series_columns)

        idx = self.series_table.currentRow()
        if idx == -1:
            author = None
        else:
            author = self.books[idx].authors()[0]

        dlg = AddEmptyBookDialog(self, self.db, author)
        if dlg.exec_() != dlg.Accepted:
            return
        num = dlg.qty_to_add
        for _x in range(num):
            idx += 1
            book = create_empty_book(dlg.selected_authors)
            self.books.insert(idx, book)
        self.series_table.setRowCount(len(self.books))
        self.renumber_series()

    def remove_book(self):
        if not question_dialog(
                self,
                _('Are you sure?'),
                '<p>' + 'Remove the selected book(s) from the series list?',
                show_copy_button=False):
            return
        rows = self.series_table.selectionModel().selectedRows()
        if len(rows) == 0:
            return
        selrows = []
        for row in rows:
            selrows.append(row.row())
        selrows.sort()
        first_sel_row = self.series_table.currentRow()
        for row in reversed(selrows):
            self.books.pop(row)
            self.series_table.removeRow(row)
        if first_sel_row < self.series_table.rowCount():
            self.series_table.select_and_scroll_to_row(first_sel_row)
        elif self.series_table.rowCount() > 0:
            self.series_table.select_and_scroll_to_row(first_sel_row - 1)
        self.renumber_series()

    def move_rows_up(self):
        self.series_table.setFocus()
        rows = self.series_table.selectionModel().selectedRows()
        if len(rows) == 0:
            return
        first_sel_row = rows[0].row()
        if first_sel_row <= 0:
            return
        # Workaround for strange selection bug in Qt which "alters" the selection
        # in certain circumstances which meant move down only worked properly "once"
        selrows = []
        for row in rows:
            selrows.append(row.row())
        selrows.sort()
        for selrow in selrows:
            self.series_table.swap_row_widgets(selrow - 1, selrow + 1)
            self.books[selrow -
                       1], self.books[selrow] = self.books[selrow], self.books[
                           selrow - 1]

        scroll_to_row = first_sel_row - 1
        if scroll_to_row > 0:
            scroll_to_row = scroll_to_row - 1
        self.series_table.scrollToItem(self.series_table.item(
            scroll_to_row, 0))
        self.renumber_series()

    def move_rows_down(self):
        self.series_table.setFocus()
        rows = self.series_table.selectionModel().selectedRows()
        if len(rows) == 0:
            return
        last_sel_row = rows[-1].row()
        if last_sel_row == self.series_table.rowCount() - 1:
            return
        # Workaround for strange selection bug in Qt which "alters" the selection
        # in certain circumstances which meant move down only worked properly "once"
        selrows = []
        for row in rows:
            selrows.append(row.row())
        selrows.sort()
        for selrow in reversed(selrows):
            self.series_table.swap_row_widgets(selrow + 2, selrow)
            self.books[selrow +
                       1], self.books[selrow] = self.books[selrow], self.books[
                           selrow + 1]

        scroll_to_row = last_sel_row + 1
        if scroll_to_row < self.series_table.rowCount() - 1:
            scroll_to_row = scroll_to_row + 1
        self.series_table.scrollToItem(self.series_table.item(
            scroll_to_row, 0))
        self.renumber_series()

    def series_indent_change(self, delta):
        for row in self.series_table.selectionModel().selectedRows():
            book = self.books[row.row()]
            series_indent = book.series_indent()
            if delta > 0:
                if series_indent < 2:
                    book.set_series_indent(series_indent + 1)
            else:
                if series_indent > 0:
                    book.set_series_indent(series_indent - 1)
            book.set_assigned_index(None)
        self.renumber_series()

    def sort_by(self, name):
        if name == 'PubDate':
            self.books = sorted(self.books,
                                key=lambda k: k.sort_key(sort_by_pubdate=True))
        elif name == 'Original Series Name':
            self.books = sorted(self.books,
                                key=lambda k: k.sort_key(sort_by_name=True))
        else:
            self.books = sorted(self.books, key=lambda k: k.sort_key())
        self.renumber_series()

    def search_web(self, name):
        URLS = {
            'FantasticFiction':
            'http://www.fantasticfiction.co.uk/search/?searchfor=author&keywords={author}',
            'Goodreads':
            'http://www.goodreads.com/search/search?q={author}&search_type=books',
            'Google':
            'http://www.google.com/#sclient=psy&q=%22{author}%22+%22{title}%22',
            'Wikipedia':
            'http://en.wikipedia.org/w/index.php?title=Special%3ASearch&search={author}'
        }
        for row in self.series_table.selectionModel().selectedRows():
            book = self.books[row.row()]
            safe_title = self.convert_to_search_text(book.title())
            safe_author = self.convert_author_to_search_text(book.authors()[0])
            url = URLS[name].replace('{title}', safe_title).replace(
                '{author}', safe_author)
            open_url(QUrl.fromEncoded(url))

    def convert_to_search_text(self, text, encoding='utf-8'):
        # First we strip characters we will definitely not want to pass through.
        # Periods from author initials etc do not need to be supplied
        text = text.replace('.', '')
        # Now encode the text using Python function with chosen encoding
        text = quote_plus(text.encode(encoding, 'ignore'))
        # If we ended up with double spaces as plus signs (++) replace them
        text = text.replace('++', '+')
        return text

    def convert_author_to_search_text(self, author, encoding='utf-8'):
        # We want to convert the author name to FN LN format if it is stored LN, FN
        # We do this because some websites (Kobo) have crappy search engines that
        # will not match Adams+Douglas but will match Douglas+Adams
        # Not really sure of the best way of determining if the user is using LN, FN
        # Approach will be to check the tweak and see if a comma is in the name

        # Comma separated author will be pipe delimited in Calibre database
        fn_ln_author = author
        if author.find(',') > -1:
            # This might be because of a FN LN,Jr - check the tweak
            sort_copy_method = tweaks['author_sort_copy_method']
            if sort_copy_method == 'invert':
                # Calibre default. Hence "probably" using FN LN format.
                fn_ln_author = author
            else:
                # We will assume that we need to switch the names from LN,FN to FN LN
                parts = author.split(',')
                surname = parts.pop(0)
                parts.append(surname)
                fn_ln_author = ' '.join(parts).strip()
        return self.convert_to_search_text(fn_ln_author, encoding)

    def cell_changed(self, row, column):
        book = self.books[row]
        if column == 0:
            book.set_title(
                unicode(self.series_table.item(row, column).text()).strip())
        elif column == 2:
            qtdate = convert_qvariant(
                self.series_table.item(row, column).data(Qt.DisplayRole))
            book.set_pubdate(qt_to_dt(qtdate, as_utc=False))

    def item_selection_changed(self):
        row = self.series_table.currentRow()
        if row == -1:
            return
        has_assigned_index = False
        for row in self.series_table.selectionModel().selectedRows():
            book = self.books[row.row()]
            if book.assigned_index():
                has_assigned_index = True
        self.series_table.clear_index_action.setEnabled(has_assigned_index)
        if not has_assigned_index:
            for book in self.books:
                if book.assigned_index():
                    has_assigned_index = True
        self.series_table.clear_all_index_action.setEnabled(has_assigned_index)