From 477d947899332fd303b3b646cac3d62c8e4a6f35 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 2 Jun 2010 23:47:54 +0100
Subject: [PATCH 1/7] Handle attempts to set tags to the empty string
---
src/calibre/gui2/dialogs/tag_list_editor.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index c2cc1d7116..427e7f44a5 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -37,6 +37,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.connect(self.available_tags, SIGNAL('itemChanged(QListWidgetItem *)'), self.finish_editing)
def finish_editing(self, item):
+ if not item.text():
+ error_dialog(self, 'Tag is blank',
+ 'A tag cannot be set to nothing. Delete it instead.'%(item.text())).exec_()
+ item.setText(self.item_before_editing.text())
+ return
if item.text() != self.item_before_editing.text():
if item.text() in self.all_tags.keys() or item.text() in self.to_rename.keys():
error_dialog(self, 'Tag already used',
From 40d054d465140be0838f736ccc1bd970c9e67f45 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 08:54:36 +0100
Subject: [PATCH 2/7] Fix tag_list_editor.py to use ids when deleting tags
---
src/calibre/gui2/dialogs/tag_list_editor.py | 9 +++++----
src/calibre/library/database2.py | 18 ++++++++++--------
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 427e7f44a5..bccd0deee9 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -48,7 +48,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
'The tag %s is already used.'%(item.text())).exec_()
item.setText(self.item_before_editing.text())
return
- id,ign = self.item_before_editing.data(Qt.UserRole).toInt()
+ (id,_) = self.item_before_editing.data(Qt.UserRole).toInt()
self.to_rename[item.text()] = id
def rename_tag(self):
@@ -82,13 +82,14 @@ class TagListEditor(QDialog, Ui_TagListEditor):
deletes += confirms
for item in deletes:
- self.to_delete.append(item)
+ (id,_) = item.data(Qt.UserRole).toInt()
+ self.to_delete.append(id)
self.available_tags.takeItem(self.available_tags.row(item))
def accept(self):
for text in self.to_rename:
- self.db.rename_tag(self.to_rename[text], unicode(text))
+ self.db.rename_tag(id=self.to_rename[text], new_name=unicode(text))
for item in self.to_delete:
- self.db.delete_tag(unicode(item.text()))
+ self.db.delete_tag_using_id(item)
QDialog.accept(self)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 0544293095..f76ae9c77a 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -987,16 +987,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Convenience method for tags_list_editor
def get_tags_with_ids(self):
- result = self.conn.get('SELECT * FROM tags')
+ result = self.conn.get('SELECT id,name FROM tags')
if not result:
- return {}
- r = []
- for k,v in result:
- r.append((k,v))
- return r
+ return []
+ return result
- def rename_tag(self, id, new):
- self.conn.execute('UPDATE tags SET name=? WHERE id=?', (new, id))
+ def rename_tag(self, id, new_name):
+ self.conn.execute('UPDATE tags SET name=? WHERE id=?', (new_name, id))
self.conn.commit()
def get_tags(self, id):
@@ -1083,6 +1080,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('DELETE FROM tags WHERE id=?', (id,))
self.conn.commit()
+ def delete_tag_using_id(self, id):
+ if id:
+ self.conn.execute('DELETE FROM books_tags_link WHERE tag=?', (id,))
+ self.conn.execute('DELETE FROM tags WHERE id=?', (id,))
+ self.conn.commit()
def set_series(self, id, series, notify=True):
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
From 55c0f6f289be93c3861d47f331277e88399e8c61 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 10:58:21 +0100
Subject: [PATCH 3/7] 1) Add 'managers' for series and publisher. 2) Option for
manager now appears only if the right-click is on/in that category 3) Avoid
the cleanup/repaints if no work was done
---
src/calibre/gui2/dialogs/tag_list_editor.py | 86 +++++++++++++--------
src/calibre/gui2/dialogs/tag_list_editor.ui | 8 +-
src/calibre/gui2/tag_view.py | 30 +++++--
src/calibre/gui2/ui.py | 9 ++-
src/calibre/library/database2.py | 55 ++++++++++---
5 files changed, 132 insertions(+), 56 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index bccd0deee9..7ee616fe1b 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -6,12 +6,11 @@ from PyQt4.QtGui import QDialog, QListWidgetItem
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
from calibre.gui2 import question_dialog, error_dialog
+from calibre.ebooks.metadata import title_sort
+
class TagListEditor(QDialog, Ui_TagListEditor):
- def tag_cmp(self, x, y):
- return cmp(x.lower(), y.lower())
-
- def __init__(self, window, db, tag_to_match):
+ def __init__(self, window, db, tag_to_match, category):
QDialog.__init__(self, window)
Ui_TagListEditor.__init__(self)
self.setupUi(self)
@@ -20,9 +19,20 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.to_delete = []
self.db = db
self.all_tags = {}
- for k,v in db.get_tags_with_ids():
+ self.category = category
+ if category == 'tags':
+ result = db.get_tags_with_ids()
+ compare = (lambda x,y:cmp(x.lower(), y.lower()))
+ elif category == 'series':
+ result = db.get_series_with_ids()
+ compare = (lambda x,y:cmp(title_sort(x).lower(), title_sort(y).lower()))
+ elif category == 'publishers':
+ result = db.get_publishers_with_ids()
+ compare = (lambda x,y:cmp(x.lower(), y.lower()))
+
+ for k,v in result:
self.all_tags[v] = k
- for tag in sorted(self.all_tags.keys(), cmp=self.tag_cmp):
+ for tag in sorted(self.all_tags.keys(), cmp=compare):
item = QListWidgetItem(tag)
item.setData(Qt.UserRole, self.all_tags[tag])
self.available_tags.addItem(item)
@@ -38,17 +48,17 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def finish_editing(self, item):
if not item.text():
- error_dialog(self, 'Tag is blank',
- 'A tag cannot be set to nothing. Delete it instead.'%(item.text())).exec_()
+ error_dialog(self, 'Item is blank',
+ 'An item cannot be set to nothing. Delete it instead.'%(item.text())).exec_()
item.setText(self.item_before_editing.text())
return
if item.text() != self.item_before_editing.text():
if item.text() in self.all_tags.keys() or item.text() in self.to_rename.keys():
- error_dialog(self, 'Tag already used',
- 'The tag %s is already used.'%(item.text())).exec_()
+ error_dialog(self, 'Item already used',
+ 'The item %s is already used.'%(item.text())).exec_()
item.setText(self.item_before_editing.text())
return
- (id,_) = self.item_before_editing.data(Qt.UserRole).toInt()
+ (id,ign) = self.item_before_editing.data(Qt.UserRole).toInt()
self.to_rename[item.text()] = id
def rename_tag(self):
@@ -57,39 +67,49 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def _rename_tag(self, item):
if item is None:
- error_dialog(self, 'No tag selected', 'You must select one tag from the list of Available tags.').exec_()
+ error_dialog(self, 'No item selected', 'You must select one item from the list of Available items.').exec_()
return
self.item_before_editing = item.clone()
item.setFlags (item.flags() | Qt.ItemIsEditable);
self.available_tags.editItem(item)
def delete_tags(self, item=None):
- confirms, deletes = [], []
- items = self.available_tags.selectedItems() if item is None else [item]
- if not items:
- error_dialog(self, 'No tags selected', 'You must select at least one tag from the list of Available tags.').exec_()
+ deletes = self.available_tags.selectedItems() if item is None else [item]
+ if not deletes:
+ error_dialog(self, 'No items selected', 'You must select at least one items from the list.').exec_()
+ return
+ ct = ', '.join([unicode(item.text()) for item in deletes])
+ if not question_dialog(self, _('Are your sure?'),
+ '
'+_('Are you certain you want to delete the following items?')+'
'+ct):
return
- for item in items:
- if self.db.is_tag_used(unicode(item.text())):
- confirms.append(item)
- else:
- deletes.append(item)
- if confirms:
- ct = ', '.join([unicode(item.text()) for item in confirms])
- if question_dialog(self, _('Are your sure?'),
- '
'+_('The following tags are used by one or more books. '
- 'Are you certain you want to delete them?')+'
'+ct):
- deletes += confirms
for item in deletes:
- (id,_) = item.data(Qt.UserRole).toInt()
+ (id,ign) = item.data(Qt.UserRole).toInt()
self.to_delete.append(id)
self.available_tags.takeItem(self.available_tags.row(item))
def accept(self):
- for text in self.to_rename:
- self.db.rename_tag(id=self.to_rename[text], new_name=unicode(text))
- for item in self.to_delete:
- self.db.delete_tag_using_id(item)
- QDialog.accept(self)
+ rename_func = None
+ if self.category == 'tags':
+ rename_func = self.db.rename_tag
+ delete_func = self.db.delete_tag_using_id
+ elif self.category == 'series':
+ rename_func = self.db.rename_series
+ delete_func = self.db.delete_series_using_id
+ elif self.category == 'publishers':
+ rename_func = self.db.rename_publisher
+ delete_func = self.db.delete_publisher_using_id
+
+ work_done = False
+ if rename_func:
+ for text in self.to_rename:
+ work_done = True
+ rename_func(id=self.to_rename[text], new_name=unicode(text))
+ for item in self.to_delete:
+ work_done = True
+ delete_func(item)
+ if not work_done:
+ QDialog.reject(self)
+ else:
+ QDialog.accept(self)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui
index 383dc875ac..4f57af745b 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.ui
+++ b/src/calibre/gui2/dialogs/tag_list_editor.ui
@@ -11,7 +11,7 @@
- Tag Editor
+ Category Editor
@@ -25,7 +25,7 @@
-
- Tags in use
+ Items in use
available_tags
@@ -54,7 +54,7 @@
-
- Delete tag from database. This will unapply the tag from all books and then remove it from the database.
+ Delete item from database. This will unapply the item from all books and then remove it from the database.
...
@@ -74,7 +74,7 @@
-
- Rename the tag everywhere it is used.
+ Rename the item in every book where it is used.
...
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 11db157ed4..fd232bb750 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -24,7 +24,7 @@ class TagsView(QTreeView): # {{{
restriction_set = pyqtSignal(object)
tags_marked = pyqtSignal(object, object)
user_category_edit = pyqtSignal(object)
- tag_list_edit = pyqtSignal(object)
+ tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
def __init__(self, *args):
@@ -91,7 +91,13 @@ class TagsView(QTreeView): # {{{
return
try:
if action == 'manage_tags':
- self.tag_list_edit.emit(category)
+ self.tag_list_edit.emit(category, 'tags')
+ return
+ if action == 'manage_series':
+ self.tag_list_edit.emit(category, 'series')
+ return
+ if action == 'manage_publishers':
+ self.tag_list_edit.emit(category, 'publishers')
return
if action == 'manage_categories':
self.user_category_edit.emit(category)
@@ -136,10 +142,24 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler, action='defaults'))
self.context_menu.addSeparator()
- self.context_menu.addAction(_('Manage Tags'),
+ if category == _('Tags'):
+ self.context_menu.addAction(_('Manage Tags'),
partial(self.context_menu_handler, action='manage_tags',
category=tag_name))
+ elif category == _('Searches'):
+ self.context_menu.addAction(_('Manage Saved Searches'),
+ partial(self.context_menu_handler, action='manage_searches',
+ category=tag_name))
+ elif category == _('Publishers'):
+ self.context_menu.addAction(_('Manage Publishers'),
+ partial(self.context_menu_handler, action='manage_publishers',
+ category=tag_name))
+ elif category == _('Series'):
+ self.context_menu.addAction(_('Manage Series'),
+ partial(self.context_menu_handler, action='manage_series',
+ category=tag_name))
+ self.context_menu.addSeparator()
if category in prefs['user_categories'].keys():
self.context_menu.addAction(_('Manage User Categories'),
partial(self.context_menu_handler, action='manage_categories',
@@ -149,10 +169,6 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler, action='manage_categories',
category=None))
- self.context_menu.addAction(_('Manage Saved Searches'),
- partial(self.context_menu_handler, action='manage_searches',
- category=tag_name))
-
self.context_menu.popup(self.mapToGlobal(point))
return True
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 773f44acd2..63172fc7a5 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -660,13 +660,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.tags_view.set_new_model()
self.tags_view.recount()
- def do_tags_list_edit(self, tag):
- d = TagListEditor(self, self.library_view.model().db, tag)
+ def do_tags_list_edit(self, tag, category):
+ d = TagListEditor(self, self.library_view.model().db, tag, category)
d.exec_()
if d.result() == d.Accepted:
+ # Clean up everything, as information could have changed for many books.
+ self.library_view.model().refresh()
self.tags_view.set_new_model()
self.tags_view.recount()
- self.library_view.model().refresh()
+ self.saved_search.clear_to_help()
+ self.search.clear_to_help()
def do_saved_search_edit(self, search):
d = SavedSearchEditor(self, search)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index f76ae9c77a..c01d6fd4d6 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -731,8 +731,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
icon=icon, tooltip = tooltip)
for r in data if item_not_zero_func(r)]
if category == 'series':
- categories[category].sort(cmp=lambda x,y:cmp(title_sort(x.name),
- title_sort(y.name)))
+ categories[category].sort(cmp=lambda x,y:cmp(title_sort(x.name).lower(),
+ title_sort(y.name).lower()))
# We delayed computing the standard formats category because it does not
# use a view, but is computed dynamically
@@ -985,7 +985,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if notify:
self.notify('metadata', [id])
- # Convenience method for tags_list_editor
+ # Convenience methods for tags_list_editor
def get_tags_with_ids(self):
result = self.conn.get('SELECT id,name FROM tags')
if not result:
@@ -996,6 +996,49 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('UPDATE tags SET name=? WHERE id=?', (new_name, id))
self.conn.commit()
+ def delete_tag_using_id(self, id):
+ if id:
+ self.conn.execute('DELETE FROM books_tags_link WHERE tag=?', (id,))
+ self.conn.execute('DELETE FROM tags WHERE id=?', (id,))
+ self.conn.commit()
+
+ def get_series_with_ids(self):
+ result = self.conn.get('SELECT id,name FROM series')
+ if not result:
+ return []
+ return result
+
+ def rename_series(self, id, new_name):
+ self.conn.execute('UPDATE series SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
+
+ def delete_series_using_id(self, id):
+ if id:
+ books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,))
+ for (book_id,) in books:
+ self.conn.execute('UPDATE books SET series_index=1.0 WHERE id=?', (book_id,))
+ self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,))
+ self.conn.execute('DELETE FROM series WHERE id=?', (id,))
+ self.conn.commit()
+
+ def get_publishers_with_ids(self):
+ result = self.conn.get('SELECT id,name FROM publishers')
+ if not result:
+ return []
+ return result
+
+ def rename_publisher(self, id, new_name):
+ self.conn.execute('UPDATE publishers SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
+
+ def delete_publisher_using_id(self, id):
+ if id:
+ self.conn.execute('DELETE FROM books_publishers_link WHERE publisher=?', (id,))
+ self.conn.execute('DELETE FROM publishers WHERE id=?', (id,))
+ self.conn.commit()
+
+ # end convenience methods
+
def get_tags(self, id):
result = self.conn.get(
'SELECT name FROM tags WHERE id IN (SELECT tag FROM books_tags_link WHERE book=?)',
@@ -1080,12 +1123,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('DELETE FROM tags WHERE id=?', (id,))
self.conn.commit()
- def delete_tag_using_id(self, id):
- if id:
- self.conn.execute('DELETE FROM books_tags_link WHERE tag=?', (id,))
- self.conn.execute('DELETE FROM tags WHERE id=?', (id,))
- self.conn.commit()
-
def set_series(self, id, series, notify=True):
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))
self.conn.execute('DELETE FROM series WHERE (SELECT COUNT(id) FROM books_series_link WHERE series=series.id) < 1')
From 07837f6d4742c701b7c1629091d4efe768b89076 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 13:10:40 +0100
Subject: [PATCH 4/7] 1) On tag pane editing of series, publisher, tags, search
2) a few cleanups, such as sorting the hidden category list
---
src/calibre/gui2/dialogs/tag_list_editor.py | 4 +-
src/calibre/gui2/tag_view.py | 81 ++++++++++++++++-----
src/calibre/gui2/ui.py | 2 +
src/calibre/utils/search_query_parser.py | 6 ++
4 files changed, 74 insertions(+), 19 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 7ee616fe1b..3741226fc9 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -26,7 +26,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
elif category == 'series':
result = db.get_series_with_ids()
compare = (lambda x,y:cmp(title_sort(x).lower(), title_sort(y).lower()))
- elif category == 'publishers':
+ elif category == 'publisher':
result = db.get_publishers_with_ids()
compare = (lambda x,y:cmp(x.lower(), y.lower()))
@@ -96,7 +96,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
elif self.category == 'series':
rename_func = self.db.rename_series
delete_func = self.db.delete_series_using_id
- elif self.category == 'publishers':
+ elif self.category == 'publisher':
rename_func = self.db.rename_publisher
delete_func = self.db.delete_publisher_using_id
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index fd232bb750..8179188a87 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -20,12 +20,14 @@ from calibre.utils.search_query_parser import saved_searches
class TagsView(QTreeView): # {{{
- need_refresh = pyqtSignal()
+ refresh_required = pyqtSignal()
restriction_set = pyqtSignal(object)
tags_marked = pyqtSignal(object, object)
user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
+ tag_item_renamed = pyqtSignal()
+ search_item_renamed = pyqtSignal()
def __init__(self, *args):
QTreeView.__init__(self, *args)
@@ -36,7 +38,8 @@ class TagsView(QTreeView): # {{{
def set_database(self, db, tag_match, popularity, restriction):
self.hidden_categories = config['tag_browser_hidden_categories']
- self._model = TagsModel(db, parent=self, hidden_categories=self.hidden_categories)
+ self._model = TagsModel(db, parent=self,
+ hidden_categories=self.hidden_categories)
self.popularity = popularity
self.restriction = restriction
self.tag_match = tag_match
@@ -48,12 +51,12 @@ class TagsView(QTreeView): # {{{
self.popularity.setChecked(config['sort_by_popularity'])
self.popularity.stateChanged.connect(self.sort_changed)
self.restriction.activated[str].connect(self.search_restriction_set)
- self.need_refresh.connect(self.recount, type=Qt.QueuedConnection)
+ self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
db.add_listener(self.database_changed)
self.saved_searches_changed(recount=False)
def database_changed(self, event, ids):
- self.need_refresh.emit()
+ self.refresh_required.emit()
@property
def match_all(self):
@@ -80,16 +83,23 @@ class TagsView(QTreeView): # {{{
if event.button() == Qt.LeftButton:
QTreeView.mouseReleaseEvent(self, event)
+ def mouseDoubleClickEvent(self, event):
+ # swallow these to avoid toggling and editing at the same time
+ pass
+
def toggle(self, index):
modifiers = int(QApplication.keyboardModifiers())
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
if self._model.toggle(index, exclusive):
self.tags_marked.emit(self._model.tokens(), self.match_all)
- def context_menu_handler(self, action=None, category=None):
+ def context_menu_handler(self, action=None, category=None, index=None):
if not action:
return
try:
+ if action == 'edit_item':
+ self.edit(index)
+ return
if action == 'manage_tags':
self.tag_list_edit.emit(category, 'tags')
return
@@ -97,7 +107,7 @@ class TagsView(QTreeView): # {{{
self.tag_list_edit.emit(category, 'series')
return
if action == 'manage_publishers':
- self.tag_list_edit.emit(category, 'publishers')
+ self.tag_list_edit.emit(category, 'publisher')
return
if action == 'manage_categories':
self.user_category_edit.emit(category)
@@ -123,24 +133,31 @@ class TagsView(QTreeView): # {{{
item = index.internalPointer()
tag_name = ''
if item.type == TagTreeItem.TAG:
+ tag_item = item
tag_name = item.tag.name
item = item.parent
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
self.context_menu = QMenu(self)
- self.context_menu.addAction(_('Hide %s') % category,
- partial(self.context_menu_handler, action='hide', category=category))
-
- if self.hidden_categories:
+ # If the user right-clicked on a tag/series/publisher, then offer
+ # the possibility of renaming that item
+ if tag_name and item.category_key in ['tags', 'series', 'publisher', 'search']:
+ self.context_menu.addAction(_('Rename item') + " '" + tag_name + "'",
+ partial(self.context_menu_handler, action='edit_item',
+ category=tag_item, index=index))
self.context_menu.addSeparator()
+ # Hide/Show/Restore categories
+ self.context_menu.addAction(_('Hide category %s') % category,
+ partial(self.context_menu_handler, action='hide', category=category))
+ if self.hidden_categories:
m = self.context_menu.addMenu(_('Show category'))
- for col in self.hidden_categories:
+ for col in sorted(self.hidden_categories, cmp=lambda x,y: cmp(x.lower(), y.lower())):
m.addAction(col,
partial(self.context_menu_handler, action='show', category=col))
- self.context_menu.addSeparator()
- self.context_menu.addAction(_('Restore defaults'),
+ self.context_menu.addAction(_('Show all categories'),
partial(self.context_menu_handler, action='defaults'))
+ # Offer specific editors for tags/series/publishers/saved searches
self.context_menu.addSeparator()
if category == _('Tags'):
self.context_menu.addAction(_('Manage Tags'),
@@ -159,6 +176,7 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler, action='manage_series',
category=tag_name))
+ # Always show the user categories editor
self.context_menu.addSeparator()
if category in prefs['user_categories'].keys():
self.context_menu.addAction(_('Manage User Categories'),
@@ -219,7 +237,8 @@ class TagTreeItem(object): # {{{
TAG = 1
ROOT = 2
- def __init__(self, data=None, category_icon=None, icon_map=None, parent=None, tooltip=None):
+ def __init__(self, data=None, category_icon=None, icon_map=None,
+ parent=None, tooltip=None, category_key=None):
self.parent = parent
self.children = []
if self.parent is not None:
@@ -234,6 +253,7 @@ class TagTreeItem(object): # {{{
self.bold_font = QFont()
self.bold_font.setBold(True)
self.bold_font = QVariant(self.bold_font)
+ self.category_key = category_key
elif self.type == self.TAG:
icon_map[0] = data.icon
self.tag, self.icon_state_map = data, list(map(QVariant, icon_map))
@@ -279,6 +299,8 @@ class TagTreeItem(object): # {{{
return QVariant('%s'%(self.tag.name))
else:
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
+ if role == Qt.EditRole:
+ return QVariant(self.tag.name)
if role == Qt.DecorationRole:
return self.icon_state_map[self.tag.state]
if role == Qt.ToolTipRole and self.tag.tooltip is not None:
@@ -293,7 +315,7 @@ class TagTreeItem(object): # {{{
class TagsModel(QAbstractItemModel): # {{{
- def __init__(self, db, parent=None, hidden_categories=None):
+ def __init__(self, db, parent, hidden_categories=None):
QAbstractItemModel.__init__(self, parent)
# must do this here because 'QPixmap: Must construct a QApplication
@@ -313,6 +335,7 @@ class TagsModel(QAbstractItemModel): # {{{
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
self.db = db
+ self.tags_view = parent
self.hidden_categories = hidden_categories
self.search_restriction = ''
self.ignore_next_search = 0
@@ -340,7 +363,7 @@ class TagsModel(QAbstractItemModel): # {{{
c = TagTreeItem(parent=self.root_item,
data=self.categories[i],
category_icon=self.category_icon_map[r],
- tooltip=tt)
+ tooltip=tt, category_key=r)
for tag in data[r]:
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
@@ -398,11 +421,35 @@ class TagsModel(QAbstractItemModel): # {{{
item = index.internalPointer()
return item.data(role)
+ def setData(self, index, value, role=Qt.EditRole):
+ if not index.isValid():
+ return NONE
+ val = unicode(value.toString())
+ item = index.internalPointer()
+ if item.parent.category_key == 'series':
+ self.db.rename_series(item.tag.id, val)
+ item.tag.name = val
+ self.tags_view.tag_item_renamed.emit()
+ elif item.parent.category_key == 'publisher':
+ self.db.rename_publisher(item.tag.id, val)
+ item.tag.name = val
+ self.tags_view.tag_item_renamed.emit()
+ elif item.parent.category_key == 'tags':
+ self.db.rename_tag(item.tag.id, val)
+ item.tag.name = val
+ self.tags_view.tag_item_renamed.emit()
+ elif item.parent.category_key == 'search':
+ saved_searches.rename(unicode(item.data(role).toString()), val)
+ item.tag.name = val
+ self.tags_view.search_item_renamed.emit()
+ self.dataChanged.emit(index, index)
+ return True
+
def headerData(self, *args):
return NONE
def flags(self, *args):
- return Qt.ItemIsEnabled|Qt.ItemIsSelectable
+ return Qt.ItemIsEnabled|Qt.ItemIsSelectable|Qt.ItemIsEditable
def path_for_index(self, index):
ans = []
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 63172fc7a5..4d1555a84a 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -553,6 +553,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
+ self.tags_view.tag_item_renamed.connect(self.library_view.model().refresh)
+ self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
self.search.search.connect(self.tags_view.model().reinit)
for x in (self.location_view.count_changed, self.tags_view.recount,
self.restriction_count_changed):
diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py
index 509adb49d4..d6bf932b76 100644
--- a/src/calibre/utils/search_query_parser.py
+++ b/src/calibre/utils/search_query_parser.py
@@ -52,6 +52,12 @@ class SavedSearchQueries(object):
self.queries.pop(self.force_unicode(name), False)
prefs[self.opt_name] = self.queries
+ def rename(self, old_name, new_name):
+ self.queries[self.force_unicode(new_name)] = \
+ self.queries.get(self.force_unicode(old_name), None)
+ self.queries.pop(self.force_unicode(old_name), False)
+ prefs[self.opt_name] = self.queries
+
def names(self):
return sorted(self.queries.keys(),
cmp=lambda x,y: cmp(x.lower(), y.lower()))
From 8233bd3a8974e62ceddafa444f8059ea32f54be6 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 14:37:55 +0100
Subject: [PATCH 5/7] In-place editing of author names
---
src/calibre/gui2/dialogs/tag_list_editor.py | 15 ++++----
src/calibre/gui2/tag_view.py | 29 ++++++++++++---
src/calibre/library/database2.py | 40 +++++++++++++++++----
3 files changed, 66 insertions(+), 18 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 3741226fc9..dc06a8897e 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -48,14 +48,14 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def finish_editing(self, item):
if not item.text():
- error_dialog(self, 'Item is blank',
- 'An item cannot be set to nothing. Delete it instead.'%(item.text())).exec_()
+ error_dialog(self, _('Item is blank'),
+ _('An item cannot be set to nothing. Delete it instead.')).exec_()
item.setText(self.item_before_editing.text())
return
if item.text() != self.item_before_editing.text():
if item.text() in self.all_tags.keys() or item.text() in self.to_rename.keys():
- error_dialog(self, 'Item already used',
- 'The item %s is already used.'%(item.text())).exec_()
+ error_dialog(self, _('Item already used'),
+ _('The item %s is already used.')%(item.text())).exec_()
item.setText(self.item_before_editing.text())
return
(id,ign) = self.item_before_editing.data(Qt.UserRole).toInt()
@@ -67,7 +67,8 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def _rename_tag(self, item):
if item is None:
- error_dialog(self, 'No item selected', 'You must select one item from the list of Available items.').exec_()
+ error_dialog(self, _('No item selected'),
+ _('You must select one item from the list of Available items.')).exec_()
return
self.item_before_editing = item.clone()
item.setFlags (item.flags() | Qt.ItemIsEditable);
@@ -76,7 +77,8 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def delete_tags(self, item=None):
deletes = self.available_tags.selectedItems() if item is None else [item]
if not deletes:
- error_dialog(self, 'No items selected', 'You must select at least one items from the list.').exec_()
+ error_dialog(self, _('No items selected'),
+ _('You must select at least one items from the list.')).exec_()
return
ct = ', '.join([unicode(item.text()) for item in deletes])
if not question_dialog(self, _('Are your sure?'),
@@ -112,4 +114,3 @@ class TagListEditor(QDialog, Ui_TagListEditor):
QDialog.reject(self)
else:
QDialog.accept(self)
-
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 8179188a87..6946f337d6 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -17,6 +17,7 @@ from calibre.gui2 import config, NONE
from calibre.utils.config import prefs
from calibre.library.field_metadata import TagsIcons
from calibre.utils.search_query_parser import saved_searches
+from calibre.gui2 import error_dialog
class TagsView(QTreeView): # {{{
@@ -141,7 +142,7 @@ class TagsView(QTreeView): # {{{
self.context_menu = QMenu(self)
# If the user right-clicked on a tag/series/publisher, then offer
# the possibility of renaming that item
- if tag_name and item.category_key in ['tags', 'series', 'publisher', 'search']:
+ if tag_name and item.category_key in ['authors', 'tags', 'series', 'publisher', 'search']:
self.context_menu.addAction(_('Rename item') + " '" + tag_name + "'",
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
@@ -381,8 +382,12 @@ class TagsModel(QAbstractItemModel): # {{{
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map)
tb_categories = self.db.field_metadata
+ self.category_items = {}
for category in tb_categories:
if category in data: # They should always be there, but ...
+ # make a map of sets of names per category for duplicate
+ # checking when editing
+ self.category_items[category] = [tag.name for tag in data[category]]
self.row_map.append(category)
self.categories.append(tb_categories[category]['name'])
@@ -425,23 +430,37 @@ class TagsModel(QAbstractItemModel): # {{{
if not index.isValid():
return NONE
val = unicode(value.toString())
+ if not val:
+ error_dialog(self.tags_view, _('Item is blank'),
+ _('An item cannot be set to nothing. Delete it instead.')).exec_()
+ return False
+
item = index.internalPointer()
- if item.parent.category_key == 'series':
+ key = item.parent.category_key
+ if val in self.category_items[key]:
+ error_dialog(self.tags_view, 'Duplicate item',
+ _('The name %s is already used.')%val).exec_()
+ return False
+ if key == 'series':
self.db.rename_series(item.tag.id, val)
item.tag.name = val
self.tags_view.tag_item_renamed.emit()
- elif item.parent.category_key == 'publisher':
+ elif key == 'publisher':
self.db.rename_publisher(item.tag.id, val)
item.tag.name = val
self.tags_view.tag_item_renamed.emit()
- elif item.parent.category_key == 'tags':
+ elif key == 'tags':
self.db.rename_tag(item.tag.id, val)
item.tag.name = val
self.tags_view.tag_item_renamed.emit()
- elif item.parent.category_key == 'search':
+ elif key == 'search':
saved_searches.rename(unicode(item.data(role).toString()), val)
item.tag.name = val
self.tags_view.search_item_renamed.emit()
+ elif key == 'authors':
+ self.db.rename_author(item.tag.id, val)
+ item.tag.name = val
+ self.tags_view.tag_item_renamed.emit()
self.dataChanged.emit(index, index)
return True
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index c01d6fd4d6..860859061c 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -993,8 +993,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_tag(self, id, new_name):
- self.conn.execute('UPDATE tags SET name=? WHERE id=?', (new_name, id))
- self.conn.commit()
+ if id:
+ self.conn.execute('UPDATE tags SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
def delete_tag_using_id(self, id):
if id:
@@ -1009,8 +1010,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_series(self, id, new_name):
- self.conn.execute('UPDATE series SET name=? WHERE id=?', (new_name, id))
- self.conn.commit()
+ if id:
+ self.conn.execute('UPDATE series SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
def delete_series_using_id(self, id):
if id:
@@ -1028,8 +1030,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_publisher(self, id, new_name):
- self.conn.execute('UPDATE publishers SET name=? WHERE id=?', (new_name, id))
- self.conn.commit()
+ if id:
+ self.conn.execute('UPDATE publishers SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
def delete_publisher_using_id(self, id):
if id:
@@ -1037,6 +1040,31 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('DELETE FROM publishers WHERE id=?', (id,))
self.conn.commit()
+ def rename_author(self, id, new_name):
+ if id:
+ # Make sure that any commas in new_name are changed to '|'!
+ new_name = new_name.replace(',', '|')
+ self.conn.execute('UPDATE authors SET name=? WHERE id=?', (new_name, id))
+ self.conn.commit()
+ # now must fix up the books
+ books = self.conn.get('SELECT book from books_authors_link WHERE author=?', (id,))
+ for (book_id,) in books:
+ # First, must refresh the cache to see the new authors
+ self.data.refresh_ids(self, [book_id])
+ # now fix the filesystem paths
+ self.set_path(book_id, index_is_id=True)
+ # Next fix the author sort. Reset it to the default
+ authors = self.conn.get('''
+ SELECT authors.name
+ FROM authors, books_authors_link as bl
+ WHERE bl.book = ? and bl.author = authors.id
+ ''' , (book_id,))
+ # unpack the double-list structure
+ for i,aut in enumerate(authors):
+ authors[i] = aut[0]
+ ss = authors_to_sort_string(authors)
+ self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id))
+
# end convenience methods
def get_tags(self, id):
From f12b0ff54ba54b8897dcade7f70fb1bd0f1a48eb Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 15:31:29 +0100
Subject: [PATCH 6/7] 1) add in-place editing of author in tags view 2) editor
and in-place editing of custom column 3) changed strings in tag_list_edit to
be translatable 4) various cleanups
---
src/calibre/gui2/dialogs/tag_list_editor.py | 11 +++-
src/calibre/gui2/tag_view.py | 70 +++++++++------------
src/calibre/gui2/ui.py | 8 ++-
src/calibre/library/custom_columns.py | 34 ++++++++++
4 files changed, 80 insertions(+), 43 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index dc06a8897e..fa05518992 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -1,11 +1,12 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
+
+from functools import partial
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtGui import QDialog, QListWidgetItem
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
from calibre.gui2 import question_dialog, error_dialog
-
from calibre.ebooks.metadata import title_sort
class TagListEditor(QDialog, Ui_TagListEditor):
@@ -29,6 +30,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
elif category == 'publisher':
result = db.get_publishers_with_ids()
compare = (lambda x,y:cmp(x.lower(), y.lower()))
+ else: # should be a custom field
+ self.cc_label = db.field_metadata[category]['label']
+ print 'here', self.cc_label
+ result = self.db.get_custom_items_with_ids(label=self.cc_label)
+ compare = (lambda x,y:cmp(x.lower(), y.lower()))
for k,v in result:
self.all_tags[v] = k
@@ -101,6 +107,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
elif self.category == 'publisher':
rename_func = self.db.rename_publisher
delete_func = self.db.delete_publisher_using_id
+ else:
+ rename_func = partial(self.db.rename_custom_item, label=self.cc_label)
+ delete_func = partial(self.db.delete_custom_item_using_id, label=self.cc_label)
work_done = False
if rename_func:
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 6946f337d6..e9e5228dfc 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -94,21 +94,16 @@ class TagsView(QTreeView): # {{{
if self._model.toggle(index, exclusive):
self.tags_marked.emit(self._model.tokens(), self.match_all)
- def context_menu_handler(self, action=None, category=None, index=None):
+ def context_menu_handler(self, action=None, category=None,
+ key=None, index=None):
if not action:
return
try:
if action == 'edit_item':
self.edit(index)
return
- if action == 'manage_tags':
- self.tag_list_edit.emit(category, 'tags')
- return
- if action == 'manage_series':
- self.tag_list_edit.emit(category, 'series')
- return
- if action == 'manage_publishers':
- self.tag_list_edit.emit(category, 'publisher')
+ if action == 'open_editor':
+ self.tag_list_edit.emit(category, key)
return
if action == 'manage_categories':
self.user_category_edit.emit(category)
@@ -139,10 +134,13 @@ class TagsView(QTreeView): # {{{
item = item.parent
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
+ key = item.category_key
self.context_menu = QMenu(self)
# If the user right-clicked on a tag/series/publisher, then offer
# the possibility of renaming that item
- if tag_name and item.category_key in ['authors', 'tags', 'series', 'publisher', 'search']:
+ if tag_name and \
+ (key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
+ self.db.field_metadata[key]['is_custom']):
self.context_menu.addAction(_('Rename item') + " '" + tag_name + "'",
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
@@ -160,22 +158,15 @@ class TagsView(QTreeView): # {{{
# Offer specific editors for tags/series/publishers/saved searches
self.context_menu.addSeparator()
- if category == _('Tags'):
- self.context_menu.addAction(_('Manage Tags'),
- partial(self.context_menu_handler, action='manage_tags',
- category=tag_name))
- elif category == _('Searches'):
+ if key in ['tags', 'publisher', 'series'] or \
+ self.db.field_metadata[key]['is_custom']:
+ self.context_menu.addAction(_('Manage ') + category,
+ partial(self.context_menu_handler, action='open_editor',
+ category=tag_name, key=key))
+ elif key == 'search':
self.context_menu.addAction(_('Manage Saved Searches'),
partial(self.context_menu_handler, action='manage_searches',
category=tag_name))
- elif category == _('Publishers'):
- self.context_menu.addAction(_('Manage Publishers'),
- partial(self.context_menu_handler, action='manage_publishers',
- category=tag_name))
- elif category == _('Series'):
- self.context_menu.addAction(_('Manage Series'),
- partial(self.context_menu_handler, action='manage_series',
- category=tag_name))
# Always show the user categories editor
self.context_menu.addSeparator()
@@ -441,27 +432,24 @@ class TagsModel(QAbstractItemModel): # {{{
error_dialog(self.tags_view, 'Duplicate item',
_('The name %s is already used.')%val).exec_()
return False
- if key == 'series':
- self.db.rename_series(item.tag.id, val)
- item.tag.name = val
- self.tags_view.tag_item_renamed.emit()
- elif key == 'publisher':
- self.db.rename_publisher(item.tag.id, val)
- item.tag.name = val
- self.tags_view.tag_item_renamed.emit()
- elif key == 'tags':
- self.db.rename_tag(item.tag.id, val)
- item.tag.name = val
- self.tags_view.tag_item_renamed.emit()
- elif key == 'search':
+ if key == 'search':
saved_searches.rename(unicode(item.data(role).toString()), val)
- item.tag.name = val
self.tags_view.search_item_renamed.emit()
- elif key == 'authors':
- self.db.rename_author(item.tag.id, val)
- item.tag.name = val
+ else:
+ if key == 'series':
+ self.db.rename_series(item.tag.id, val)
+ elif key == 'publisher':
+ self.db.rename_publisher(item.tag.id, val)
+ elif key == 'tags':
+ self.db.rename_tag(item.tag.id, val)
+ elif key == 'authors':
+ self.db.rename_author(item.tag.id, val)
+ elif self.db.field_metadata[key]['is_custom']:
+ self.db.rename_custom_item(item.tag.id, val,
+ label=self.db.field_metadata[key]['label'])
self.tags_view.tag_item_renamed.emit()
- self.dataChanged.emit(index, index)
+ item.tag.name = val
+ self.refresh()
return True
def headerData(self, *args):
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 4d1555a84a..7546e461d6 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -553,7 +553,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
- self.tags_view.tag_item_renamed.connect(self.library_view.model().refresh)
+ self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
self.search.search.connect(self.tags_view.model().reinit)
for x in (self.location_view.count_changed, self.tags_view.recount,
@@ -673,6 +673,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.saved_search.clear_to_help()
self.search.clear_to_help()
+ def do_tag_item_renamed(self):
+ # Clean up library view and search
+ self.library_view.model().refresh()
+ self.saved_search.clear_to_help()
+ self.search.clear_to_help()
+
def do_saved_search_edit(self, search):
d = SavedSearchEditor(self, search)
d.exec_()
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 83e6b029cb..64a261d935 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -171,6 +171,40 @@ class CustomColumns(object):
ans.sort(cmp=lambda x,y:cmp(x.lower(), y.lower()))
return ans
+ # convenience methods for tag editing
+ def get_custom_items_with_ids(self, label=None, num=None):
+ if label is not None:
+ data = self.custom_column_label_map[label]
+ if num is not None:
+ data = self.custom_column_num_map[num]
+ table, lt = self.custom_table_names(data['num'])
+ if not data['normalized']:
+ return []
+ ans = self.conn.get('SELECT id, value FROM %s'%table)
+ return ans
+
+ def rename_custom_item(self, id, new_name, label=None, num=None):
+ if id:
+ if label is not None:
+ data = self.custom_column_label_map[label]
+ if num is not None:
+ data = self.custom_column_num_map[num]
+ table, lt = self.custom_table_names(data['num'])
+ self.conn.execute('UPDATE %s SET value=? WHERE id=?'%table, (new_name, id))
+ self.conn.commit()
+
+ def delete_custom_item_using_id(self, id, label=None, num=None):
+ if id:
+ if label is not None:
+ data = self.custom_column_label_map[label]
+ if num is not None:
+ data = self.custom_column_num_map[num]
+ table, lt = self.custom_table_names(data['num'])
+ self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id,))
+ self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id,))
+ self.conn.commit()
+ # end convenience methods
+
def all_custom(self, label=None, num=None):
if label is not None:
data = self.custom_column_label_map[label]
From eb205aee6f8eb67fbd26ffef7fb660fe6b4189b2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 19:25:23 +0100
Subject: [PATCH 7/7] A few changes to improve robustness. Also some cosmetic
changes.
---
src/calibre/gui2/dialogs/tag_list_editor.py | 9 ++++++---
src/calibre/gui2/tag_view.py | 18 ++++++++++++++----
src/calibre/library/custom_columns.py | 6 +++---
src/calibre/library/database2.py | 8 ++++++--
4 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index fa05518992..1ec80f4b4a 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -31,9 +31,12 @@ class TagListEditor(QDialog, Ui_TagListEditor):
result = db.get_publishers_with_ids()
compare = (lambda x,y:cmp(x.lower(), y.lower()))
else: # should be a custom field
- self.cc_label = db.field_metadata[category]['label']
- print 'here', self.cc_label
- result = self.db.get_custom_items_with_ids(label=self.cc_label)
+ self.cc_label = None
+ if category in db.field_metadata:
+ self.cc_label = db.field_metadata[category]['label']
+ result = self.db.get_custom_items_with_ids(label=self.cc_label)
+ else:
+ result = []
compare = (lambda x,y:cmp(x.lower(), y.lower()))
for k,v in result:
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index e9e5228dfc..49ed03945f 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -135,8 +135,12 @@ class TagsView(QTreeView): # {{{
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
key = item.category_key
+ # Verify that we are working with a field that we know something about
+ if key not in self.db.field_metadata:
+ return True
+
self.context_menu = QMenu(self)
- # If the user right-clicked on a tag/series/publisher, then offer
+ # If the user right-clicked on an editable item, then offer
# the possibility of renaming that item
if tag_name and \
(key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
@@ -378,7 +382,7 @@ class TagsModel(QAbstractItemModel): # {{{
if category in data: # They should always be there, but ...
# make a map of sets of names per category for duplicate
# checking when editing
- self.category_items[category] = [tag.name for tag in data[category]]
+ self.category_items[category] = set([tag.name for tag in data[category]])
self.row_map.append(category)
self.categories.append(tb_categories[category]['name'])
@@ -425,13 +429,16 @@ class TagsModel(QAbstractItemModel): # {{{
error_dialog(self.tags_view, _('Item is blank'),
_('An item cannot be set to nothing. Delete it instead.')).exec_()
return False
-
item = index.internalPointer()
key = item.parent.category_key
+ # make certain we know about the category
+ if key not in self.db.field_metadata:
+ return
if val in self.category_items[key]:
error_dialog(self.tags_view, 'Duplicate item',
_('The name %s is already used.')%val).exec_()
return False
+ oldval = item.tag.name
if key == 'search':
saved_searches.rename(unicode(item.data(role).toString()), val)
self.tags_view.search_item_renamed.emit()
@@ -449,7 +456,10 @@ class TagsModel(QAbstractItemModel): # {{{
label=self.db.field_metadata[key]['label'])
self.tags_view.tag_item_renamed.emit()
item.tag.name = val
- self.refresh()
+ self.dataChanged.emit(index, index)
+ # replace the old value in the duplicate detection map with the new one
+ self.category_items[key].discard(oldval)
+ self.category_items[key].add(val)
return True
def headerData(self, *args):
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 64a261d935..4d2c8970b6 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -177,7 +177,7 @@ class CustomColumns(object):
data = self.custom_column_label_map[label]
if num is not None:
data = self.custom_column_num_map[num]
- table, lt = self.custom_table_names(data['num'])
+ table,lt = self.custom_table_names(data['num'])
if not data['normalized']:
return []
ans = self.conn.get('SELECT id, value FROM %s'%table)
@@ -189,7 +189,7 @@ class CustomColumns(object):
data = self.custom_column_label_map[label]
if num is not None:
data = self.custom_column_num_map[num]
- table, lt = self.custom_table_names(data['num'])
+ table,lt = self.custom_table_names(data['num'])
self.conn.execute('UPDATE %s SET value=? WHERE id=?'%table, (new_name, id))
self.conn.commit()
@@ -199,7 +199,7 @@ class CustomColumns(object):
data = self.custom_column_label_map[label]
if num is not None:
data = self.custom_column_num_map[num]
- table, lt = self.custom_table_names(data['num'])
+ table,lt = self.custom_table_names(data['num'])
self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id,))
self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id,))
self.conn.commit()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 860859061c..e7bb5c2060 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -986,6 +986,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.notify('metadata', [id])
# Convenience methods for tags_list_editor
+ # Note: we generally do not need to refresh_ids because library_view will
+ # refresh everything.
def get_tags_with_ids(self):
result = self.conn.get('SELECT id,name FROM tags')
if not result:
@@ -1017,11 +1019,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def delete_series_using_id(self, id):
if id:
books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,))
- for (book_id,) in books:
- self.conn.execute('UPDATE books SET series_index=1.0 WHERE id=?', (book_id,))
self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,))
self.conn.execute('DELETE FROM series WHERE id=?', (id,))
self.conn.commit()
+ for (book_id,) in books:
+ self.conn.execute('UPDATE books SET series_index=1.0 WHERE id=?', (book_id,))
def get_publishers_with_ids(self):
result = self.conn.get('SELECT id,name FROM publishers')
@@ -1040,6 +1042,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('DELETE FROM publishers WHERE id=?', (id,))
self.conn.commit()
+ # There is no editor for author, so we do not need get_authors_with_ids or
+ # delete_author_using_id.
def rename_author(self, id, new_name):
if id:
# Make sure that any commas in new_name are changed to '|'!