Improve the manage authors dialog

This commit is contained in:
Kovid Goyal 2011-05-06 08:30:56 -06:00
commit 2dd33e9c25
6 changed files with 175 additions and 20 deletions

View File

@ -3,7 +3,8 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__license__ = 'GPL v3' __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.ebooks.metadata import author_to_author_sort
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
@ -19,7 +20,7 @@ class tableItem(QTableWidgetItem):
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 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) QDialog.__init__(self, parent)
Ui_EditAuthorsDialog.__init__(self) Ui_EditAuthorsDialog.__init__(self)
self.setupUi(self) self.setupUi(self)
@ -30,14 +31,23 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.buttonBox.accepted.connect(self.accepted) self.buttonBox.accepted.connect(self.accepted)
# Set up the column headings
self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setColumnCount(2) 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 = {} self.authors = {}
auts = db.get_authors_with_ids() auts = db.get_authors_with_ids()
self.table.setRowCount(len(auts)) self.table.setRowCount(len(auts))
setattr(self.table, '__lt__', lambda x, y: True if strcmp(x, y) < 0 else False)
select_item = None select_item = None
for row, (id, author, sort) in enumerate(auts): for row, (id, author, sort) in enumerate(auts):
author = author.replace('|', ',') author = author.replace('|', ',')
@ -48,7 +58,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setItem(row, 0, aut) self.table.setItem(row, 0, aut)
self.table.setItem(row, 1, sort) self.table.setItem(row, 1, sort)
if id == id_to_select: if id == id_to_select:
if select_sort:
select_item = sort select_item = sort
else:
select_item = aut
self.table.resizeColumnsToContents() self.table.resizeColumnsToContents()
# set up the cellChanged signal only after the table is filled # set up the cellChanged signal only after the table is filled
@ -69,23 +82,83 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort) self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)
# Position on the desired item
if select_item is not None: if select_item is not None:
self.table.setCurrentItem(select_item) self.table.setCurrentItem(select_item)
self.table.editItem(select_item) self.table.editItem(select_item)
self.start_find_pos = select_item.row() * 2 + select_item.column()
else: else:
self.table.setCurrentCell(0, 0) 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): def do_sort_by_author(self):
self.author_order = 1 if self.author_order == 0 else 0 self.author_order = 1 if self.author_order == 0 else 0
self.table.sortByColumn(0, self.author_order) self.table.sortByColumn(0, self.author_order)
self.sort_by_author.setChecked(True) self.sort_by_author.setChecked(True)
self.sort_by_author_sort.setChecked(False) 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): def do_sort_by_author_sort(self):
self.author_sort_order = 1 if self.author_sort_order == 0 else 0 self.author_sort_order = 1 if self.author_sort_order == 0 else 0
self.table.sortByColumn(1, self.author_sort_order) self.table.sortByColumn(1, self.author_sort_order)
self.sort_by_author.setChecked(False) self.sort_by_author.setChecked(False)
self.sort_by_author_sort.setChecked(True) 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): def accepted(self):
self.result = [] self.result = []

View File

@ -20,6 +20,50 @@
<string>Manage authors</string> <string>Manage authors</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="">
<item>
<widget class="QLabel">
<property name="text">
<string>&amp;Search for:</string>
</property>
<property name="buddy">
<cstring>find_box</cstring>
</property>
</widget>
</item>
<item>
<widget class="HistoryLineEdit" name="find_box">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="find_button">
<property name="text">
<string>F&amp;ind</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<widget class="QTableWidget" name="table"> <widget class="QTableWidget" name="table">
<property name="sizePolicy"> <property name="sizePolicy">
@ -143,4 +187,11 @@ after changing Preferences-&gt;Advanced-&gt;Tweaks-&gt;Author sort name algorith
</hints> </hints>
</connection> </connection>
</connections> </connections>
<customwidgets>
<customwidget>
<class>HistoryLineEdit</class>
<extends>QComboBox</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
</customwidgets>
</ui> </ui>

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import textwrap, re, os import textwrap, re, os
from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QMessageBox,
QIcon, QToolButton, QWidget, QLabel, QGridLayout, QIcon, QToolButton, QWidget, QLabel, QGridLayout,
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDoubleSpinBox, QListWidgetItem, QSize, QPixmap,
QPushButton, QSpinBox, QLineEdit, QSizePolicy) QPushButton, QSpinBox, QLineEdit, QSizePolicy)
@ -22,7 +22,7 @@ from calibre.ebooks.metadata import (title_sort, authors_to_string,
string_to_authors, check_isbn) string_to_authors, check_isbn)
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE,
choose_files, error_dialog, choose_images, question_dialog) choose_files, error_dialog, choose_images)
from calibre.utils.date import local_tz, qt_to_dt from calibre.utils.date import local_tz, qt_to_dt
from calibre import strftime from calibre import strftime
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
@ -31,6 +31,16 @@ from calibre.utils.date import utcfromtimestamp
from calibre.gui2.comments_editor import Editor from calibre.gui2.comments_editor import Editor
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
from calibre.gui2.dialogs.tag_editor import TagEditor 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 The interface common to all widgets used to set basic metadata
@ -168,16 +178,22 @@ class AuthorsEdit(MultiCompleteComboBox):
def manage_authors(self): def manage_authors(self):
if self.original_val != self.current_val: 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 ' _('You have changed the authors for this book. You must save '
'these changes before you can use Manage authors. Do you ' '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.commit(self.db, self.id_)
self.db.commit() self.db.commit()
self.original_val = self.current_val self.original_val = self.current_val
else: else:
self.current_val = self.original_val 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
first_author_id = self.db.get_author_id(first_author) if first_author else None
self.dialog.parent().do_author_sort_edit(self, first_author_id,
select_sort=False)
self.initialize(self.db, self.id_) self.initialize(self.db, self.id_)
self.dialog.author_sort.initialize(self.db, self.id_) self.dialog.author_sort.initialize(self.db, self.id_)
@ -256,7 +272,7 @@ class AuthorSortEdit(EnLineEdit):
'No action is required if this is what you want.')) 'No action is required if this is what you want.'))
self.tooltips = (ok_tooltip, bad_tooltip) self.tooltips = (ok_tooltip, bad_tooltip)
self.authors_edit.editTextChanged.connect(self.update_state) self.authors_edit.editTextChanged.connect(self.update_state_and_val)
self.textChanged.connect(self.update_state) self.textChanged.connect(self.update_state)
autogen_button.clicked.connect(self.auto_generate) autogen_button.clicked.connect(self.auto_generate)
@ -278,12 +294,19 @@ class AuthorSortEdit(EnLineEdit):
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def update_state_and_val(self):
au = unicode(self.authors_edit.text())
# Handle case change if the authors box changed
if strcmp(au, self.current_val) == 0:
self.current_val = au
self.update_state()
def update_state(self, *args): def update_state(self, *args):
au = unicode(self.authors_edit.text()) au = unicode(self.authors_edit.text())
au = re.sub(r'\s+et al\.$', '', au) au = re.sub(r'\s+et al\.$', '', au)
au = self.db.author_sort_from_authors(string_to_authors(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: if normal:
col = 'rgb(0, 255, 0, 20%)' col = 'rgb(0, 255, 0, 20%)'
else: else:
@ -316,11 +339,10 @@ class AuthorSortEdit(EnLineEdit):
self.current_val = self.db.author_sort_from_authors(authors) self.current_val = self.db.author_sort_from_authors(authors)
def initialize(self, db, id_): 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_): def commit(self, db, id_):
aus = self.current_val 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 return True
@ -919,10 +941,13 @@ class TagsEdit(MultiCompleteLineEdit): # {{{
def edit(self, db, id_): def edit(self, db, id_):
if self.changed: 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' _('You have changed the tags. In order to use the tags'
' editor, you must either discard or apply these ' ' 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_) self.commit(db, id_)
db.commit() db.commit()
self.original_val = self.current_val self.original_val = self.current_val

View File

@ -2048,12 +2048,12 @@ class TagBrowserMixin(object): # {{{
self.library_view.select_rows(ids) self.library_view.select_rows(ids)
# refreshing the tags view happens at the emit()/call() site # 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 Open the manage authors dialog
''' '''
db = self.library_view.model().db db = self.library_view.model().db
editor = EditAuthorsDialog(parent, db, id) editor = EditAuthorsDialog(parent, db, id, select_sort)
d = editor.exec_() d = editor.exec_()
if d: if d:
for (id, old_author, new_author, new_sort) in editor.result: for (id, old_author, new_author, new_sort) in editor.result:

View File

@ -2285,6 +2285,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return [] return []
return result 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): def set_sort_field_for_author(self, old_id, new_sort, commit=True, notify=False):
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \ self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
(new_sort.strip(), old_id)) (new_sort.strip(), old_id))

View File

@ -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: 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 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. * 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 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. * The author sort box will be red if the author sort value differs from what |app| thinks it should be.