From 477d947899332fd303b3b646cac3d62c8e4a6f35 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Wed, 2 Jun 2010 23:47:54 +0100
Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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 06/17] 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 07/17] 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 '|'!
From f79daf62e95321e2ebd39c45fa1f541bc49f5d1f Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 12:27:33 -0600
Subject: [PATCH 08/17] ...
---
src/calibre/gui2/dialogs/saved_search_editor.py | 6 ++----
src/calibre/manual/gui.rst | 4 ++--
src/calibre/web/feeds/templates.py | 2 +-
3 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/calibre/gui2/dialogs/saved_search_editor.py b/src/calibre/gui2/dialogs/saved_search_editor.py
index a9382201b9..6a8b790625 100644
--- a/src/calibre/gui2/dialogs/saved_search_editor.py
+++ b/src/calibre/gui2/dialogs/saved_search_editor.py
@@ -3,14 +3,12 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal '
-from PyQt4.QtCore import SIGNAL, Qt
-from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem
+from PyQt4.QtCore import SIGNAL
+from PyQt4.QtGui import QDialog
from calibre.gui2.dialogs.saved_search_editor_ui import Ui_SavedSearchEditor
-from calibre.utils.config import prefs
from calibre.utils.search_query_parser import saved_searches
from calibre.gui2.dialogs.confirm_delete import confirm
-from calibre.constants import islinux
class SavedSearchEditor(QDialog, Ui_SavedSearchEditor):
diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst
index 0d79c77e55..98e31b2252 100644
--- a/src/calibre/manual/gui.rst
+++ b/src/calibre/manual/gui.rst
@@ -241,9 +241,9 @@ Now, you can access your saved search in the Tag Browser under "Searches". A sin
.. _configuration:
-Configuration
+Preferences
---------------
-The configuration dialog allows you to set some global defaults used by all of |app|. To access it, click the |cbi|.
+The Preferences dialog allows you to set some global defaults used by all of |app|. To access it, click the |cbi|.
.. |cbi| image:: images/configuration.png
diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py
index af0c8da6b4..2a003c14f8 100644
--- a/src/calibre/web/feeds/templates.py
+++ b/src/calibre/web/feeds/templates.py
@@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal '
from lxml import html, etree
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \
- STRONG, BR, H1, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \
+ STRONG, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \
TABLE, TD, TR
from calibre import preferred_encoding, strftime, isbytestring
From 4ced832557f72bcb1cba38e1c29f3fad4e45f4c7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 13:06:04 -0600
Subject: [PATCH 09/17] Increase timeout when adding files to 10 minutes
---
src/calibre/gui2/add.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py
index 131692a2c2..64743e914b 100644
--- a/src/calibre/gui2/add.py
+++ b/src/calibre/gui2/add.py
@@ -222,6 +222,8 @@ class DBAdder(Thread):
class Adder(QObject):
+ ADD_TIMEOUT = 600 # seconds
+
def __init__(self, parent, db, callback, spare_server=None):
QObject.__init__(self, parent)
self.pd = ProgressDialog(_('Adding...'), parent=parent)
@@ -328,7 +330,7 @@ class Adder(QObject):
except Empty:
pass
- if (time.time() - self.last_added_at) > 300:
+ if (time.time() - self.last_added_at) > self.ADD_TIMEOUT:
self.timer.stop()
self.pd.hide()
self.db_adder.end = True
From 4f43a79419a4c2e66b6cab4fc0d066327142f719 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 14:40:52 -0600
Subject: [PATCH 10/17] Call clean method to remove orphans on db integrity
check. Also remove orphans from the builtin link tables
---
src/calibre/library/database2.py | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index e7bb5c2060..09ebff4cdf 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -643,11 +643,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
'''
Remove orphaned entries.
'''
- st = 'DELETE FROM %(table)s WHERE (SELECT COUNT(id) FROM books_%(ltable)s_link WHERE %(ltable_col)s=%(table)s.id) < 1;'
- self.conn.execute(st%dict(ltable='authors', table='authors', ltable_col='author'))
- self.conn.execute(st%dict(ltable='publishers', table='publishers', ltable_col='publisher'))
- self.conn.execute(st%dict(ltable='tags', table='tags', ltable_col='tag'))
- self.conn.execute(st%dict(ltable='series', table='series', ltable_col='series'))
+ def doit(ltable, table, ltable_col):
+ st = ('DELETE FROM books_%s_link WHERE (SELECT COUNT(id) '
+ 'FROM books WHERE id=book) < 1;')%ltable
+ self.conn.execute(st)
+ st = ('DELETE FROM %(table)s WHERE (SELECT COUNT(id) '
+ 'FROM books_%(ltable)s_link WHERE '
+ '%(ltable_col)s=%(table)s.id) < 1;') % dict(
+ ltable=ltable, table=table, ltable_col=ltable_col)
+ self.conn.execute(st)
+
+ for ltable, table, ltable_col in [
+ ('authors', 'authors', 'author'),
+ ('publishers', 'publishers', 'publisher'),
+ ('tags', 'tags', 'tag'),
+ ('series', 'series', 'series')
+ ]:
+ doit(ltable, table, ltable_col)
+
for id_, tag in self.conn.get('SELECT id, name FROM tags', all=True):
if not tag.strip():
self.conn.execute('DELETE FROM books_tags_link WHERE tag=?',
@@ -1674,6 +1687,7 @@ books_series_link feeds
def check_integrity(self, callback):
callback(0., _('Checking SQL integrity...'))
+ self.clean()
user_version = self.user_version
sql = '\n'.join(self.conn.dump())
self.conn.close()
From c8ce9a87fe7a6ade9ecac2570fe352aa910d590d Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 14:44:01 -0600
Subject: [PATCH 11/17] Fix series sorting by popularity in the Tag Browser
---
src/calibre/library/database2.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 09ebff4cdf..29e1901ce2 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -743,7 +743,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
icon=icon, tooltip = tooltip)
for r in data if item_not_zero_func(r)]
- if category == 'series':
+ if category == 'series' and not sort_on_count:
categories[category].sort(cmp=lambda x,y:cmp(title_sort(x.name).lower(),
title_sort(y.name).lower()))
From 585dadd241fadfd6057afad3d46a9b38d30c6768 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Thu, 3 Jun 2010 21:49:24 +0100
Subject: [PATCH 12/17] Make ondevice searchable
---
src/calibre/library/field_metadata.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 07c90119bd..243e3646da 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -195,11 +195,11 @@ class FieldMetadata(dict):
'is_category':False}),
('ondevice', {'table':None,
'column':None,
- 'datatype':'bool',
+ 'datatype':'text',
'is_multiple':None,
'kind':'field',
'name':None,
- 'search_terms':[],
+ 'search_terms':['ondevice'],
'is_custom':False,
'is_category':False}),
('path', {'table':None,
From cc9457da00b8741f6713656be7c49ce088831971 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 22:06:05 -0600
Subject: [PATCH 13/17] Ensure showing a column never results in a size zero
column
---
src/calibre/gui2/library/views.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 7f6edd1b3d..eb9ffe6258 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -75,6 +75,9 @@ class BooksView(QTableView): # {{{
h.setSectionHidden(idx, True)
elif action == 'show':
h.setSectionHidden(idx, False)
+ if h.sectionSize(idx) < 3:
+ sz = h.sectionSizeHint(idx)
+ h.resizeSection(idx, sz)
elif action == 'ascending':
self.sortByColumn(idx, Qt.AscendingOrder)
elif action == 'descending':
@@ -257,6 +260,11 @@ class BooksView(QTableView): # {{{
for col, alignment in state.get('column_alignment', {}).items():
self._model.change_alignment(col, alignment)
+ for i in range(h.count()):
+ if not h.isSectionHidden(i) and h.sectionSize(i) < 3:
+ sz = h.sectionSizeHint(i)
+ h.resizeSection(i, sz)
+
def get_default_state(self):
old_state = {
'hidden_columns': [],
From 2f9e4ee00d84932dd00e3ae0919f2a2c3b4c2c7c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Thu, 3 Jun 2010 23:03:06 -0600
Subject: [PATCH 14/17] Add a prepreprocess_html callback to the news download
code
---
src/calibre/manual/news_recipe.rst | 2 ++
src/calibre/web/feeds/news.py | 19 +++++++++++++++++--
src/calibre/web/fetch/simple.py | 3 +++
3 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/src/calibre/manual/news_recipe.rst b/src/calibre/manual/news_recipe.rst
index c840cefb53..14cc41d436 100644
--- a/src/calibre/manual/news_recipe.rst
+++ b/src/calibre/manual/news_recipe.rst
@@ -111,6 +111,8 @@ Pre/post processing of downloaded HTML
.. automember:: BasicNewsRecipe.remove_javascript
+.. automethod:: BasicNewsRecipe.prepreprocess_html
+
.. automethod:: BasicNewsRecipe.preprocess_html
.. automethod:: BasicNewsRecipe.postprocess_html
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index 79cec4a2a7..59f1fbd57d 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -403,10 +403,25 @@ class BasicNewsRecipe(Recipe):
return url
return article.get('link', None)
+ def prepreprocess_html(self, soup):
+ '''
+ This method is called with the source of each downloaded :term:`HTML` file, before
+ any of the cleanup attributes like remove_tags, keep_only_tags are
+ applied. Note that preprocess_regexps will have already been applied.
+ It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
+ It should return `soup` after processing it.
+
+ `soup`: A `BeautifulSoup `_
+ instance containing the downloaded :term:`HTML`.
+ '''
+ return soup
+
+
def preprocess_html(self, soup):
'''
This method is called with the source of each downloaded :term:`HTML` file, before
- it is parsed for links and images.
+ it is parsed for links and images. It is called after the cleanup as
+ specified by remove_tags etc.
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
It should return `soup` after processing it.
@@ -603,7 +618,7 @@ class BasicNewsRecipe(Recipe):
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
- 'preprocess_html', 'remove_tags_after',
+ 'prepreprocess_html', 'preprocess_html', 'remove_tags_after',
'remove_tags_before', 'is_link_wanted'):
setattr(self.web2disk_options, extra, getattr(self, extra))
self.web2disk_options.postprocess_html = self._postprocess_html
diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py
index c1f0d912d6..bde91ec0d2 100644
--- a/src/calibre/web/fetch/simple.py
+++ b/src/calibre/web/fetch/simple.py
@@ -136,6 +136,7 @@ class RecursiveFetcher(object):
self.remove_tags_before = getattr(options, 'remove_tags_before', None)
self.keep_only_tags = getattr(options, 'keep_only_tags', [])
self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup)
+ self.prepreprocess_html_ext = getattr(options, 'prepreprocess_html', lambda soup: soup)
self.postprocess_html_ext= getattr(options, 'postprocess_html', None)
self._is_link_wanted = getattr(options, 'is_link_wanted',
default_is_link_wanted)
@@ -153,6 +154,8 @@ class RecursiveFetcher(object):
nmassage.append((re.compile(r'', re.DOTALL), lambda m: ''))
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
+ soup = self.prepreprocess_html_ext(soup)
+
if self.keep_only_tags:
body = Tag(soup, 'body')
try:
From 0c40b8e8eefd87df39960e364dbbc79c889aaaae Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 4 Jun 2010 00:08:23 -0600
Subject: [PATCH 15/17] Fix #5679 (CHM file unable to be converted to mobi)
---
src/calibre/ebooks/mobi/writer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index e3a8f53685..ca5256b430 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -1334,7 +1334,7 @@ class MobiWriter(object):
item = self._oeb.manifest.hrefs[href]
try:
data = rescale_image(item.data, self._imagemax)
- except IOError:
+ except:
self._oeb.logger.warn('Bad image file %r' % item.href)
continue
self._records.append(data)
From a60b414c7e088d5b45531e7cc198349f09a4a4b9 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 4 Jun 2010 07:05:42 -0600
Subject: [PATCH 16/17] Improved recipe for Welt Online
---
resources/recipes/welt.recipe | 45 +++++++++++++++++++++++++++++++----
1 file changed, 40 insertions(+), 5 deletions(-)
diff --git a/resources/recipes/welt.recipe b/resources/recipes/welt.recipe
index 59d900f53e..89e0d42c09 100644
--- a/resources/recipes/welt.recipe
+++ b/resources/recipes/welt.recipe
@@ -21,12 +21,16 @@ class weltDe(BasicNewsRecipe):
no_stylesheets = True
remove_stylesheets = True
remove_javascript = True
- encoding = 'iso-8859-1'
- BasicNewsRecipe.summary_length = 200
+ encoding = 'utf-8'
+ html2epub_options = 'linearize_tables = True\nbase_font_size2=10'
+ BasicNewsRecipe.summary_length = 100
remove_tags = [dict(id='jumplinks'),
dict(id='ad1'),
+ dict(id='top'),
+ dict(id='header'),
+ dict(id='additionalNavWrapper'),
dict(id='fullimage_index'),
dict(id='additionalNav'),
dict(id='printMenu'),
@@ -35,6 +39,8 @@ class weltDe(BasicNewsRecipe):
dict(id='servicesBox'),
dict(id='servicesNav'),
dict(id='ad2'),
+ dict(id='banner_1'),
+ dict(id='ssoInfoTop'),
dict(id='brandingWrapper'),
dict(id='links-intern'),
dict(id='navigation'),
@@ -53,10 +59,22 @@ class weltDe(BasicNewsRecipe):
dict(id='xmsg_comment'),
dict(id='additionalNavWrapper'),
dict(id='imagebox'),
+ dict(id='footerContainer'),
#dict(id=''),
dict(name='span'),
dict(name='div', attrs={'class':'printURL'}),
+ dict(name='ul', attrs={'class':'clear mainNavigation inline'}),
+ dict(name='ul', attrs={'class':'inline'}),
+ dict(name='ul', attrs={'class':'ubar'}),
+ dict(name='hr', attrs={'class':'ubar'}),
+ dict(name='li', attrs={'class':'counter'}),
+ dict(name='li', attrs={'class':'browseBack'}),
+ dict(name='li', attrs={'class':'browseNext'}),
+ dict(name='li', attrs={'class':'selected'}),
+ dict(name='div', attrs={'class':'floatLeft'}),
dict(name='div', attrs={'class':'ad'}),
+ dict(name='div', attrs={'class':'ftBarLeft'}),
+ dict(name='div', attrs={'class':'clear additionalNav'}),
dict(name='div', attrs={'class':'inlineBox inlineFurtherLinks'}),
dict(name='div', attrs={'class':'inlineBox videoInlineBox'}),
dict(name='div', attrs={'class':'inlineGallery'}),
@@ -65,6 +83,23 @@ class weltDe(BasicNewsRecipe):
dict(name='div', attrs={'class':'articleOptions clear'}),
dict(name='div', attrs={'class':'noPrint galleryIndex'}),
dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}),
+ dict(name='div', attrs={'class':'clear module writeComment bgColor1'}),
+ dict(name='div', attrs={'class':'clear module textGallery bgColor1'}),
+ dict(name='div', attrs={'class':'clear module socialMedia bgColor1'}),
+ dict(name='div', attrs={'class':'clear module continuativeLinks'}),
+ dict(name='div', attrs={'class':'moreArtH3'}),
+ dict(name='div', attrs={'class':'jqmWindow'}),
+ dict(name='div', attrs={'class':'clear gap4'}),
+ dict(name='div', attrs={'class':'hidden'}),
+ dict(name='div', attrs={'class':'advertising'}),
+ dict(name='div', attrs={'class':'ad adMarginBottom'}),
+ dict(name='div', attrs={'class':'ad'}),
+ dict(name='div', attrs={'class':'topLine'}),
+ dict(name='div', attrs={'class':'toplineH2'}),
+ dict(name='div', attrs={'class':'headLineH3'}),
+ dict(name='div', attrs={'class':'print'}),
+ dict(name='div', attrs={'class':'clear menu'}),
+ dict(name='div', attrs={'class':'clear galleryContent'}),
dict(name='p', attrs={'class':'jump'}),
dict(name='a', attrs={'class':'commentLink'}),
dict(name='h2', attrs={'class':'jumpHeading'}),
@@ -75,7 +110,7 @@ class weltDe(BasicNewsRecipe):
dict(name='table', attrs={'class':'textGallery'}),
dict(name='li', attrs={'class':'active'})]
- remove_tags_after = [dict(id='tw_link_widget')]
+ remove_tags_after = [dict(name='div', attrs={'class':'clear departmentLine'})]
extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;}
@@ -87,7 +122,6 @@ class weltDe(BasicNewsRecipe):
.photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} '''
feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'),
- ('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'),
('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'),
('Finanzen', 'http://welt.de/finanzen/?service=Rss'),
('Sport', 'http://welt.de/sport/?service=Rss'),
@@ -101,4 +135,5 @@ class weltDe(BasicNewsRecipe):
def print_version(self, url):
- return url.replace ('.html', '.html?print=yes')
+ return url.replace ('.html', '.html?print=true')
+
From 4a1dcdb2105746764f813f1f2d341fa83b7efc8c Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Fri, 4 Jun 2010 07:11:20 -0600
Subject: [PATCH 17/17] Fix #5685 (Updated recipe and icon for Clarin)
---
resources/images/news/clarin.png | Bin 330 -> 820 bytes
resources/recipes/clarin.recipe | 51 +++++++++++++++++--------------
2 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/resources/images/news/clarin.png b/resources/images/news/clarin.png
index f08bc3e5acdf925a067d44b639ce4f43fcaaad5b..2ef634678e4cb964fc1e4b1d8a571f617adfc90b 100644
GIT binary patch
literal 820
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b
zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87?@%`T^vI!PM1!;?Hv**a;!dkzVX?kx2N1>
zHC^64v81s>W7mb~0Fj%lid|K{8jBYEYhKa8)U2_qN#E8*|DyAS;x9t2E-I-DrzN~v
zVr;dn_}snK?S*GbVm92|5b-+t{;wa?S`u%t>ZHsQNWNh3r1~_c+x@-Ezhzo`m%lv_
z#cj~D)hv7M)k*%`A3wk1{TmRxT-NmKt&Nqt({@*1UAYP<*|6YAMzX)avx1jb
zW}9*_oqhf}b8_Y9X>Z#31RQ$~N;n%>xRk%Htc%^<*y-N+{oA8k^MyXv3UNequM+Dh
zlea&*ow@zz#WHqQx46B_R_|;*v-x>Nr$y7jC6|3xJxVgX6OwFL^Rd<@;m18@HTzn%
z?~hc|lA1OYnyQ?NxNx~Ssy!#lf6F4K@PPmGLpmDXE6>!KnW7pxT`yrazmWayQ%xHU
z15`y;+}hc^;)Kc9Og4vwC%#A2d=b#zo}2vSboj6HbCln_*Uz{#VNucfcZ)u$GOTe8
zzaqS~ud$k6a!>XAf((vMS5Eii9dg-eA3vqKe*Tlcr7zSs^NOyL5M$^Y4>sX$#fcm}
z{gZF+YTf#_e}$hK=i!2HT>EPSL>xacE~t3=l=aCUmB*!9b@tb_zDf~#9+SDBvGuNt
z1CQ0xlS`Z)eR2)*eSKkNnxH1D!PGfFeg7PMcl-1z<+BUCCT{6S=$Uy~q3J-$RIQ&C
zO{&ZNgPv6u_ZV}lJYr^k(bLSu6!TbWm08}5_lE=pZO$`G{NoO4x4FLbdAI~H)v1=a
zMwFx^mZVxG7o`Fz1|tJQ16>0%T?3O419K}w11ke_AlJ&kVCNs_9uy6^`6-!cl_(l4
ctqcvVOe`Q8*0(=e3)H~i>FVdQ&MBb@04I=SZvX%Q
literal 330
zcmeAS@N?(olHy`uVBq!ia0vp^LO?9Q!3HFy+4N(86kC$Fy9>iV5PU6hr3om)S>O>_
z45U54*zIJt9Z0aOz%d<2gY`2A^6{Jia*91&978Nlubt@4bwGiK^||N!`lltqQQFVg
z_w@=rGU(jU**NjYfq8b!5`oD~KU^g}7QFl?w8bDMAZ2N<3&*X4Pxg2_o}Te;b|7PD
zQO=fRH^q!?7sF@wEeqW8q{RK6e`Trbgua)dH@%r(F`d%MefI7YLjm)yXf4jUuTE?Q
zTCZB-8c~v5l$uzQs+$5N7>o=IEOZTxbd3!|49u-ejjc=!bqy@63=9}2EVv2Nkei>9
YnN~?aL!9EZw?GXHp00i_>zopr001Xx4*&oF
diff --git a/resources/recipes/clarin.recipe b/resources/recipes/clarin.recipe
index 3a96bca162..7bbb663d1d 100644
--- a/resources/recipes/clarin.recipe
+++ b/resources/recipes/clarin.recipe
@@ -5,7 +5,6 @@ __copyright__ = '2008-2010, Darko Miletic '
clarin.com
'''
-from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Clarin(BasicNewsRecipe):
@@ -18,11 +17,12 @@ class Clarin(BasicNewsRecipe):
max_articles_per_feed = 100
use_embedded_content = False
no_stylesheets = True
- cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg')
- encoding = 'cp1252'
- language = 'es'
- masthead_url = 'http://www.clarin.com/shared/v10/img/Hd/lg_Clarin.gif'
- extra_css = ' body{font-family: Arial,Helvetica,sans-serif} h2{font-family: Georgia,"Times New Roman",Times,serif; font-size: xx-large} .Volan,.Pie,.Autor{ font-size: x-small} .Copete,.Hora{font-size: large} '
+ encoding = 'utf8'
+ language = 'es_AR'
+ publication_type = 'newspaper'
+ INDEX = 'http://www.clarin.com'
+ masthead_url = 'http://www.clarin.com/static/CLAClarin/images/logo-clarin-print.jpg'
+ extra_css = ' body{font-family: Arial,Helvetica,sans-serif} h2{font-family: Georgia,serif; font-size: xx-large} .hora{font-weight:bold} .hd p{font-size: small} .nombre-autor{color: #0F325A} '
conversion_options = {
'comment' : description
@@ -31,27 +31,32 @@ class Clarin(BasicNewsRecipe):
, 'language' : language
}
- remove_tags = [
- dict(name='a' , attrs={'class':'Imp' })
- ,dict(name='div' , attrs={'class':'Perma' })
- ,dict(name='h1' , text='Imprimir' )
- ]
+ keep_only_tags = [dict(attrs={'class':['hd','mt']})]
feeds = [
- (u'Ultimo Momento', u'http://www.clarin.com/diario/hoy/um/sumariorss.xml')
- ,(u'El Pais' , u'http://www.clarin.com/diario/hoy/elpais.xml' )
- ,(u'Opinion' , u'http://www.clarin.com/diario/hoy/opinion.xml' )
- ,(u'El Mundo' , u'http://www.clarin.com/diario/hoy/elmundo.xml' )
- ,(u'Sociedad' , u'http://www.clarin.com/diario/hoy/sociedad.xml' )
- ,(u'La Ciudad' , u'http://www.clarin.com/diario/hoy/laciudad.xml' )
- ,(u'Policiales' , u'http://www.clarin.com/diario/hoy/policiales.xml' )
- ,(u'Deportes' , u'http://www.clarin.com/diario/hoy/deportes.xml' )
+ (u'Pagina principal', u'http://www.clarin.com/rss/' )
+ ,(u'Politica' , u'http://www.clarin.com/rss/politica/' )
+ ,(u'Deportes' , u'http://www.clarin.com/rss/deportes/' )
+ ,(u'Economia' , u'http://www.clarin.com/economia/' )
+ ,(u'Mundo' , u'http://www.clarin.com/rss/mundo/' )
+ ,(u'Espectaculos' , u'http://www.clarin.com/rss/espectaculos/')
+ ,(u'Sociedad' , u'http://www.clarin.com/rss/sociedad/' )
+ ,(u'Ciudades' , u'http://www.clarin.com/rss/ciudades/' )
+ ,(u'Policiales' , u'http://www.clarin.com/rss/policiales/' )
+ ,(u'Internet' , u'http://www.clarin.com/rss/internet/' )
+ ,(u'Ciudades' , u'http://www.clarin.com/rss/ciudades/' )
]
def print_version(self, url):
- rest = url.partition('-0')[-1]
- lmain = rest.partition('.')[0]
- lurl = u'http://www.servicios.clarin.com/notas/jsp/clarin/v9/notas/imprimir.jsp?pagid=' + lmain
- return lurl
+ return url + '?print=1'
+ def get_cover_url(self):
+ cover_url = None
+ soup = self.index_to_soup(self.INDEX)
+ cover_item = soup.find('div',attrs={'class':'bb-md bb-md-edicion_papel'})
+ if cover_item:
+ ap = cover_item.find('a',attrs={'href':'/edicion-impresa/'})
+ if ap:
+ cover_url = self.INDEX + ap.img['src']
+ return cover_url