From 399d94bd3f2d0af2b0cf5da7077d70f5c187683b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 6 May 2011 06:04:16 +0100 Subject: [PATCH 1/3] In edit metadata single: 1) Correct implementation of author case change 2) Add a cancel button to the 'save' dialogs 3) Select the first author in Manage Authors --- .../gui2/dialogs/edit_authors_dialog.py | 7 ++- src/calibre/gui2/metadata/basic_widgets.py | 51 ++++++++++++++----- src/calibre/gui2/tag_view.py | 4 +- src/calibre/library/database2.py | 6 +++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index eae189e04c..bc1b83cfc9 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -19,7 +19,7 @@ class tableItem(QTableWidgetItem): class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): - def __init__(self, parent, db, id_to_select): + def __init__(self, parent, db, id_to_select, select_sort): QDialog.__init__(self, parent) Ui_EditAuthorsDialog.__init__(self) self.setupUi(self) @@ -48,7 +48,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setItem(row, 0, aut) self.table.setItem(row, 1, sort) if id == id_to_select: - select_item = sort + if select_sort: + select_item = sort + else: + select_item = aut self.table.resizeColumnsToContents() # set up the cellChanged signal only after the table is filled diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 50a8e7c0b8..bd438c2a19 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -8,8 +8,9 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' import textwrap, re, os +from functools import partial -from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, +from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QMessageBox, QIcon, QToolButton, QWidget, QLabel, QGridLayout, QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QPushButton, QSpinBox, QLineEdit, QSizePolicy) @@ -31,6 +32,16 @@ from calibre.utils.date import utcfromtimestamp from calibre.gui2.comments_editor import Editor from calibre.library.comments import comments_to_html from calibre.gui2.dialogs.tag_editor import TagEditor +from calibre.utils.icu import strcmp + +def save_dialog(parent, title, msg, det_msg=''): + d = QMessageBox(parent) + d.setWindowTitle(title) + d.setText(msg) + d.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + return d.exec_() + + ''' The interface common to all widgets used to set basic metadata @@ -168,16 +179,22 @@ class AuthorsEdit(MultiCompleteComboBox): def manage_authors(self): if self.original_val != self.current_val: - if (question_dialog(self, _('Authors changed'), + d = save_dialog(self, _('Authors changed'), _('You have changed the authors for this book. You must save ' 'these changes before you can use Manage authors. Do you ' - 'want to save these changes?'), show_copy_button=False)): + 'want to save these changes?')) + if d == QMessageBox.Cancel: + return + if d == QMessageBox.Yes: self.commit(self.db, self.id_) self.db.commit() self.original_val = self.current_val else: self.current_val = self.original_val - self.dialog.parent().do_author_sort_edit(self, self.id_) + first_author = self.current_val[0] if len(self.current_val) else None + self.dialog.parent().do_author_sort_edit(self, + self.db.get_author_id(first_author), + select_sort=False) self.initialize(self.db, self.id_) self.dialog.author_sort.initialize(self.db, self.id_) @@ -256,13 +273,13 @@ class AuthorSortEdit(EnLineEdit): 'No action is required if this is what you want.')) self.tooltips = (ok_tooltip, bad_tooltip) - self.authors_edit.editTextChanged.connect(self.update_state) - self.textChanged.connect(self.update_state) + self.authors_edit.editTextChanged.connect(partial(self.update_state, True)) + self.textChanged.connect(partial(self.update_state, False)) autogen_button.clicked.connect(self.auto_generate) copy_a_to_as_action.triggered.connect(self.auto_generate) copy_as_to_a_action.triggered.connect(self.copy_to_authors) - self.update_state() + self.update_state(False) @dynamic_property def current_val(self): @@ -278,12 +295,16 @@ class AuthorSortEdit(EnLineEdit): return property(fget=fget, fset=fset) - def update_state(self, *args): + def update_state(self, modify_aus, *args): au = unicode(self.authors_edit.text()) + # Handle case change if the authors box changed + if modify_aus and strcmp(au, self.current_val) == 0: + self.current_val = au + au = re.sub(r'\s+et al\.$', '', au) au = self.db.author_sort_from_authors(string_to_authors(au)) - normal = au == self.current_val + normal = strcmp(au, self.current_val) == 0 if normal: col = 'rgb(0, 255, 0, 20%)' else: @@ -316,12 +337,11 @@ class AuthorSortEdit(EnLineEdit): self.current_val = self.db.author_sort_from_authors(authors) def initialize(self, db, id_): - self.current_val = self.original_val = db.author_sort(id_, index_is_id=True) + self.current_val = db.author_sort(id_, index_is_id=True) def commit(self, db, id_): aus = self.current_val - if aus != self.original_val: - db.set_author_sort(id_, aus, notify=False, commit=False) + db.set_author_sort(id_, aus, notify=False, commit=False) return True # }}} @@ -919,10 +939,13 @@ class TagsEdit(MultiCompleteLineEdit): # {{{ def edit(self, db, id_): if self.changed: - if question_dialog(self, _('Tags changed'), + d = save_dialog(self, _('Tags changed'), _('You have changed the tags. In order to use the tags' ' editor, you must either discard or apply these ' - 'changes. Apply changes?'), show_copy_button=False): + 'changes. Apply changes?')) + if d == QMessageBox.Cancel: + return + if d == QMessageBox.Yes: self.commit(db, id_) db.commit() self.original_val = self.current_val diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 7b68229da0..a3e39aefd2 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -2048,12 +2048,12 @@ class TagBrowserMixin(object): # {{{ self.library_view.select_rows(ids) # refreshing the tags view happens at the emit()/call() site - def do_author_sort_edit(self, parent, id): + def do_author_sort_edit(self, parent, id, select_sort=True): ''' Open the manage authors dialog ''' db = self.library_view.model().db - editor = EditAuthorsDialog(parent, db, id) + editor = EditAuthorsDialog(parent, db, id, select_sort) d = editor.exec_() if d: for (id, old_author, new_author, new_sort) in editor.result: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 065f0e8446..ac36335b79 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -2285,6 +2285,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return [] return result + def get_author_id(self, author): + author = author.replace(',', '|') + result = self.conn.get('SELECT id FROM authors WHERE name=?', + (author,), all=False) + return result + def set_sort_field_for_author(self, old_id, new_sort, commit=True, notify=False): self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \ (new_sort.strip(), old_id)) From 2a8a9782c59a2d35461c011f2f2862bb67e4e93c Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 6 May 2011 10:06:33 +0100 Subject: [PATCH 2/3] Improvements to the manual --- src/calibre/manual/metadata.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/metadata.rst b/src/calibre/manual/metadata.rst index ec3dbb08bf..72ae2b8250 100644 --- a/src/calibre/manual/metadata.rst +++ b/src/calibre/manual/metadata.rst @@ -19,7 +19,7 @@ Editing the metadata of one book at a time Click the book you want to edit and then click the :guilabel:`Edit metadata` button or press the ``E`` key. A dialog opens that allows you to edit all aspects of the metadata. It has various features to make editing faster and more efficient. A list of the commonly used tips: * You can click the button in between title and authors to swap them automatically. - * You can click the button next to author sort to automatically to have |app| automatically fill it from the author name. + * You can click the button next to author sort to have |app| automatically fill it in using the sort values stored with each author. Use the :guilabel:`Manage authors` dialog to see and change the authors' sort values. This dialog can be opened by clicking and holding the button next to author sort. * You can click the button next to tags to use the Tag Editor to manage the tags associated with the book. * The ISBN box will have a red background if you enter an invalid ISBN. It will be green for valid ISBNs * The author sort box will be red if the author sort value differs from what |app| thinks it should be. From 4d581060ed92d9e407f40abd03ad19004fe07d35 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 6 May 2011 12:55:34 +0100 Subject: [PATCH 3/3] Improvements to manage authors dialog. 1) Add sort indicators 2) Add a Search facility --- .../gui2/dialogs/edit_authors_dialog.py | 76 ++++++++++++++++++- .../gui2/dialogs/edit_authors_dialog.ui | 51 +++++++++++++ 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index bc1b83cfc9..ea16a863e9 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -3,7 +3,8 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' -from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView +from PyQt4.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, + QString, QDialogButtonBox, QFrame, QLabel, QTimer) from calibre.ebooks.metadata import author_to_author_sort from calibre.gui2 import error_dialog @@ -30,14 +31,23 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.buttonBox.accepted.connect(self.accepted) + # Set up the column headings self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.setColumnCount(2) - self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort')]) + self.down_arrow_icon = QIcon(I('arrow-down.png')) + self.up_arrow_icon = QIcon(I('arrow-up.png')) + self.blank_icon = QIcon(I('blank.png')) + self.auth_col = QTableWidgetItem(_('Author')) + self.table.setHorizontalHeaderItem(0, self.auth_col) + self.auth_col.setIcon(self.blank_icon) + self.aus_col = QTableWidgetItem(_('Author sort')) + self.table.setHorizontalHeaderItem(1, self.aus_col) + self.aus_col.setIcon(self.up_arrow_icon) + # Add the data self.authors = {} auts = db.get_authors_with_ids() self.table.setRowCount(len(auts)) - setattr(self.table, '__lt__', lambda x, y: True if strcmp(x, y) < 0 else False) select_item = None for row, (id, author, sort) in enumerate(auts): author = author.replace('|', ',') @@ -72,23 +82,83 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort) self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) + # Position on the desired item if select_item is not None: self.table.setCurrentItem(select_item) self.table.editItem(select_item) + self.start_find_pos = select_item.row() * 2 + select_item.column() else: self.table.setCurrentCell(0, 0) + self.start_find_pos = -1 + + # set up the search box + self.find_box.initialize('manage_authors_search') + self.find_box.lineEdit().returnPressed.connect(self.do_find) + self.find_box.editTextChanged.connect(self.find_text_changed) + self.find_button.clicked.connect(self.do_find) + + l = QLabel(self.table) + self.not_found_label = l + l.setFrameStyle(QFrame.StyledPanel) + l.setAutoFillBackground(True) + l.setText(_('No matches found')) + l.setAlignment(Qt.AlignVCenter) + l.resize(l.sizeHint()) + l.move(10,20) + l.setVisible(False) + self.not_found_label.move(40, 40) + 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) + + def not_found_label_timer_event(self): + self.not_found_label.setVisible(False) + + def find_text_changed(self): + self.start_find_pos = -1 + + def do_find(self): + self.not_found_label.setVisible(False) + # For some reason the button box keeps stealing the RETURN shortcut. + # Steal it back + self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False) + self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False) + st = icu_lower(unicode(self.find_box.currentText())) + + for i in range(0, self.table.rowCount()*2): + self.start_find_pos = (self.start_find_pos + 1) % (self.table.rowCount()*2) + r = (self.start_find_pos/2)%self.table.rowCount() + c = self.start_find_pos % 2 + item = self.table.item(r, c) + text = icu_lower(unicode(item.text())) + if st in text: + self.table.setCurrentItem(item) + self.table.setFocus(True) + return + # Nothing found. Pop up the little dialog for 1.5 seconds + self.not_found_label.setVisible(True) + self.not_found_label_timer.start(1500) def do_sort_by_author(self): self.author_order = 1 if self.author_order == 0 else 0 self.table.sortByColumn(0, self.author_order) self.sort_by_author.setChecked(True) self.sort_by_author_sort.setChecked(False) + self.auth_col.setIcon(self.down_arrow_icon if self.author_order + else self.up_arrow_icon) + self.aus_col.setIcon(self.blank_icon) def do_sort_by_author_sort(self): self.author_sort_order = 1 if self.author_sort_order == 0 else 0 self.table.sortByColumn(1, self.author_sort_order) self.sort_by_author.setChecked(False) self.sort_by_author_sort.setChecked(True) + self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order + else self.up_arrow_icon) + self.auth_col.setIcon(self.blank_icon) def accepted(self): self.result = [] diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.ui b/src/calibre/gui2/dialogs/edit_authors_dialog.ui index 3280245959..35abc5dac5 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.ui +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.ui @@ -20,6 +20,50 @@ Manage authors + + + + + + &Search for: + + + find_box + + + + + + + + 200 + 0 + + + + + + + + F&ind + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -143,4 +187,11 @@ after changing Preferences->Advanced->Tweaks->Author sort name algorith + + + HistoryLineEdit + QComboBox +
calibre/gui2/widgets.h
+
+