From de36d8acc9decd9937c29dd0ce4c91bda430aa67 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 11 Jun 2010 18:06:57 +0100
Subject: [PATCH 01/13] Prototype implementation of average ratings
---
src/calibre/gui2/tag_view.py | 17 +++++-
src/calibre/library/custom_columns.py | 19 +++++--
src/calibre/library/database2.py | 17 +++---
src/calibre/library/schema_upgrades.py | 73 ++++++++++++++++++++++++++
4 files changed, 115 insertions(+), 11 deletions(-)
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index bc698a3502..8b1a376bb5 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -298,7 +298,10 @@ class TagTreeItem(object): # {{{
if self.tag.count == 0:
return QVariant('%s'%(self.tag.name))
else:
- return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
+ if self.tag.avg is None:
+ return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
+ else:
+ return QVariant('[%d][%d] %s'%(self.tag.count, self.tag.avg, self.tag.name))
if role == Qt.EditRole:
return QVariant(self.tag.name)
if role == Qt.DecorationRole:
@@ -332,6 +335,7 @@ class TagsModel(QAbstractItemModel): # {{{
':custom' : QIcon(I('column.svg')),
':user' : QIcon(I('drawer.svg')),
'search' : QIcon(I('search.svg'))})
+ self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
self.icon_state_map = [None, QIcon(I('plus.svg')), QIcon(I('minus.svg'))]
self.db = db
@@ -354,7 +358,14 @@ class TagsModel(QAbstractItemModel): # {{{
data=self.categories[i],
category_icon=self.category_icon_map[r],
tooltip=tt, category_key=r)
+ # This duplicates code in refresh(). Having it here as well
+ # can save seconds during startup, because we avoid a second
+ # call to get_node_tree.
for tag in data[r]:
+ if r not in self.categories_with_ratings and \
+ not self.db.field_metadata[r]['is_custom'] and \
+ not self.db.field_metadata[r]['kind'] == 'user':
+ tag.avg = None
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
def set_search_restriction(self, s):
@@ -417,6 +428,10 @@ class TagsModel(QAbstractItemModel): # {{{
if len(data[r]) > 0:
self.beginInsertRows(category_index, 0, len(data[r])-1)
for tag in data[r]:
+ if r not in self.categories_with_ratings and \
+ not self.db.field_metadata[r]['is_custom'] and \
+ not self.db.field_metadata[r]['kind'] == 'user':
+ tag.avg = None
tag.state = state_map.get(tag.name, 0)
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
self.endInsertRows()
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 23b78f38ae..91d04a4639 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -461,14 +461,27 @@ class CustomColumns(object):
CREATE VIEW tag_browser_{table} AS SELECT
id,
value,
- (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count
+ (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count,
+ (SELECT AVG(r.rating)
+ FROM {lt},
+ books_ratings_link as bl,
+ ratings as r
+ WHERE {lt}.value={table}.id and bl.book={lt}.book and
+ r.id = bl.rating and r.rating <> 0) avg_rating
FROM {table};
CREATE VIEW tag_browser_filtered_{table} AS SELECT
id,
value,
(SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
- books_list_filter(book)) count
+ books_list_filter(book)) count,
+ (SELECT AVG(r.rating)
+ FROM {lt},
+ books_ratings_link as bl,
+ ratings as r
+ WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
+ r.id = bl.rating AND r.rating <> 0 AND
+ books_list_filter(bl.book)) avg_rating
FROM {table};
'''.format(lt=lt, table=table),
@@ -505,7 +518,7 @@ class CustomColumns(object):
END;
'''.format(table=table),
]
-
+ print lines
script = ' \n'.join(lines)
self.conn.executescript(script)
self.conn.commit()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 7b98dc4537..41ad235d01 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -56,11 +56,12 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
class Tag(object):
- def __init__(self, name, id=None, count=0, state=0, tooltip=None, icon=None):
+ def __init__(self, name, id=None, count=0, state=0, avg=0, tooltip=None, icon=None):
self.name = name
self.id = id
self.count = count
self.state = state
+ self.avg = avg/2 if avg is not None else 0
self.tooltip = tooltip
self.icon = icon
@@ -125,15 +126,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.connect()
self.is_case_sensitive = not iswindows and not isosx and \
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
- SchemaUpgrade.__init__(self)
self.initialize_dynamic()
+ SchemaUpgrade.__init__(self)
def initialize_dynamic(self):
self.conn.executescript(u'''
CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT
id,
name,
- (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count
+ (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,
+ (0) as avg_rating
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -144,7 +146,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT
id,
name,
- (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count
+ (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,
+ (0) as avg_rating
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -698,9 +701,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
continue
cn = cat['column']
if ids is None:
- query = 'SELECT id, {0}, count FROM tag_browser_{1}'.format(cn, tn)
+ query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_{1}'.format(cn, tn)
else:
- query = 'SELECT id, {0}, count FROM tag_browser_filtered_{1}'.format(cn, tn)
+ query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_filtered_{1}'.format(cn, tn)
if sort_on_count:
query += ' ORDER BY count DESC'
else:
@@ -733,7 +736,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formatter = (lambda x:unicode(x))
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
- icon=icon, tooltip = tooltip)
+ avg=r[3], icon=icon, tooltip=tooltip)
for r in data if item_not_zero_func(r)]
if category == 'series' and not sort_on_count:
if tweaks['title_series_sorting'] == 'library_order':
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index 070ad1f3a6..0660a8b136 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -292,3 +292,76 @@ class SchemaUpgrade(object):
for field in self.field_metadata.itervalues():
if field['is_category'] and not field['is_custom'] and 'link_column' in field:
create_tag_browser_view(field['table'], field['link_column'], field['column'])
+
+ def upgrade_version_11(self):
+ 'Add average rating to tag browser views'
+ def create_std_tag_browser_view(table_name, column_name, view_column_name):
+ script = ('''
+ DROP VIEW IF EXISTS tag_browser_{tn};
+ CREATE VIEW tag_browser_{tn} AS SELECT
+ id,
+ {vcn},
+ (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count,
+ (SELECT AVG(ratings.rating)
+ FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
+ WHERE tl.{cn}={tn}.id and bl.book=tl.book and
+ ratings.id = bl.rating and ratings.rating <> 0) avg_rating
+ FROM {tn};
+ DROP VIEW IF EXISTS tag_browser_filtered_{tn};
+ CREATE VIEW tag_browser_filtered_{tn} AS SELECT
+ id,
+ {vcn},
+ (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
+ {cn}={tn}.id AND books_list_filter(book)) count,
+ (SELECT AVG(ratings.rating)
+ FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
+ WHERE tl.{cn}={tn}.id and bl.book=tl.book and
+ ratings.id = bl.rating and ratings.rating <> 0 AND
+ books_list_filter(bl.book)) avg_rating
+ FROM {tn};
+
+ '''.format(tn=table_name, cn=column_name, vcn=view_column_name))
+ self.conn.executescript(script)
+
+ def create_cust_tag_browser_view(table_name, link_table_name):
+ script = '''
+ DROP VIEW IF EXISTS tag_browser_{table};
+ CREATE VIEW tag_browser_{table} AS SELECT
+ id,
+ value,
+ (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count,
+ (SELECT AVG(r.rating)
+ FROM {lt},
+ books_ratings_link as bl,
+ ratings as r
+ WHERE {lt}.value={table}.id and bl.book={lt}.book and
+ r.id = bl.rating and r.rating <> 0) avg_rating
+ FROM {table};
+
+ DROP VIEW IF EXISTS tag_browser_filtered_{table};
+ CREATE VIEW tag_browser_filtered_{table} AS SELECT
+ id,
+ value,
+ (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND
+ books_list_filter(book)) count,
+ (SELECT AVG(r.rating)
+ FROM {lt},
+ books_ratings_link as bl,
+ ratings as r
+ WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
+ r.id = bl.rating AND r.rating <> 0 AND
+ books_list_filter(bl.book)) avg_rating
+ FROM {table};
+ '''.format(lt=link_table_name, table=table_name)
+ self.conn.executescript(script)
+
+ for field in self.field_metadata.itervalues():
+ if field['is_category'] and not field['is_custom'] and 'link_column' in field:
+ create_std_tag_browser_view(field['table'], field['link_column'],
+ field['column'])
+
+ for field in self.field_metadata.itervalues():
+ if field['is_category'] and field['is_custom']:
+ link_table_name = 'books_custom_column_%d_link'%field['colnum']
+ print 'try to upgrade cust col', field['table'], link_table_name
+ create_cust_tag_browser_view(field['table'], link_table_name)
From 79202dc8336eaf4fabe618d969ba5bab3b22803a Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 11 Jun 2010 18:32:58 +0100
Subject: [PATCH 02/13] Get rid of print statement
---
src/calibre/library/custom_columns.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index 91d04a4639..c0ba91e252 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -518,7 +518,6 @@ class CustomColumns(object):
END;
'''.format(table=table),
]
- print lines
script = ' \n'.join(lines)
self.conn.executescript(script)
self.conn.commit()
From 368eced25562ed0fddf9108cc3b284ca8a4c9742 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Fri, 11 Jun 2010 18:38:40 +0100
Subject: [PATCH 03/13] Make the average stay a floating point number
---
src/calibre/gui2/tag_view.py | 2 +-
src/calibre/library/database2.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 8b1a376bb5..f1bbbe1c31 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -301,7 +301,7 @@ class TagTreeItem(object): # {{{
if self.tag.avg is None:
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
else:
- return QVariant('[%d][%d] %s'%(self.tag.count, self.tag.avg, self.tag.name))
+ return QVariant('[%d][%3.1f] %s'%(self.tag.count, self.tag.avg, self.tag.name))
if role == Qt.EditRole:
return QVariant(self.tag.name)
if role == Qt.DecorationRole:
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index d014b250ba..04acca913c 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -61,7 +61,7 @@ class Tag(object):
self.id = id
self.count = count
self.state = state
- self.avg = avg/2 if avg is not None else 0
+ self.avg = avg/2.0 if avg is not None else 0
self.tooltip = tooltip
self.icon = icon
From 97111f70a09be921f3dc65525962da23cb4e64f6 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sat, 12 Jun 2010 16:58:51 +0100
Subject: [PATCH 04/13] Add graphical representation of rating
---
src/calibre/gui2/tag_view.py | 88 ++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index f1bbbe1c31..6ada763f80 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -9,11 +9,14 @@ Browsing book collection by tags.
from itertools import izip
from functools import partial
+from math import cos, sin, pi
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
QPushButton, QWidget
+from PyQt4.Qt import QItemDelegate, QString, QPainterPath, QPen, QColor, \
+ QLinearGradient, QBrush
from calibre.gui2 import config, NONE
from calibre.utils.config import prefs
@@ -23,6 +26,90 @@ from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
+class TagDelegate(QItemDelegate):
+
+ def __init__(self, parent):
+ QItemDelegate.__init__(self, parent)
+ self._parent = parent
+
+ def paint(self, painter, option, index):
+
+ def draw_rating(rect, rating):
+ COLOR = QColor("blue")
+ if rating is None:
+ return 0
+ painter.save()
+ painter.translate(r.left(), r.top())
+ factor = r.height()/100.
+# Try the star
+# star_path = QPainterPath()
+# star_path.moveTo(90, 50)
+# for i in range(1, 5):
+# star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
+# 50 + 40 * sin(0.8 * i * pi))
+# star_path.closeSubpath()
+# star_path.setFillRule(Qt.WindingFill)
+# pen = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
+# gradient = QLinearGradient(0, 0, 0, 100)
+# gradient.setColorAt(0.0, COLOR)
+# gradient.setColorAt(1.0, COLOR)
+# painter.setBrush(QBrush(gradient))
+# painter.setClipRect(0, 0, int(r.height() * (rating/5.0)), r.height())
+# painter.scale(factor, factor)
+# painter.translate(50.0, 50.0)
+# painter.rotate(-20)
+# painter.translate(-50.0, -50.0)
+# painter.drawPath(star_path)
+# painter.restore()
+# return r.height()
+
+# Try a circle
+# gradient = QLinearGradient(0, 0, 0, 100)
+# gradient.setColorAt(0.0, COLOR)
+# gradient.setColorAt(1.0, COLOR)
+# painter.setBrush(QBrush(gradient))
+# painter.setClipRect(0, 0, int(r.height() * (rating/5.0)), r.height())
+# painter.scale(factor, factor)
+# painter.drawEllipse(0, 0, 100, 100)
+# painter.restore()
+# return r.height()
+
+# Try a rectangle
+ width = 20
+ height = 80
+ left_offset = 5
+ top_offset = 10
+ if rating > 0.0:
+ pen = QPen(COLOR, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
+ painter.setPen(pen)
+ painter.scale(factor, factor)
+ painter.drawRect(left_offset, top_offset, width, height)
+ fill_height = height*(rating/5.0)
+ gradient = QLinearGradient(0, 0, 0, 100)
+ gradient.setColorAt(0.0, COLOR)
+ gradient.setColorAt(1.0, COLOR)
+ painter.setBrush(QBrush(gradient))
+ painter.drawRect(left_offset, top_offset+(height-fill_height),
+ width, fill_height)
+ painter.restore()
+ return int ((width+left_offset*2) * factor)
+
+ item = index.internalPointer()
+ if item.type == TagTreeItem.TAG:
+ r = option.rect
+ # Paint the decoration icon
+ icon = self._parent.model().data(index, Qt.DecorationRole).toPyObject()
+ icon.paint(painter, r, Qt.AlignLeft)
+ # Paint the rating, if any
+ r.setLeft(r.left()+r.height()+5)
+ text_start = draw_rating(r, item.tag.avg)
+ # Paint the text
+ r.setLeft(r.left() + text_start+5)
+ painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
+ QString('[%d] %s'%(item.tag.count, item.tag.name)))
+ else:
+ QItemDelegate.paint(self, painter, option, index)
+
class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal()
@@ -43,6 +130,7 @@ class TagsView(QTreeView): # {{{
self.setAlternatingRowColors(True)
self.setAnimated(True)
self.setHeaderHidden(True)
+ self.setItemDelegate(TagDelegate(self))
def set_database(self, db, tag_match, popularity):
self.hidden_categories = config['tag_browser_hidden_categories']
From 8ff2f2f865e86ae93265a1efc2351861370f17cd Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 13 Jun 2010 07:46:31 +0100
Subject: [PATCH 05/13] Finish average rating display. 1) change schema upgrade
to not use field_metadata. 2) add option to preferences to show (or not) the
average rating 3) add a tweak to choose between the clipped star and the
rectangle 4) fix bug -- missing set of the search_as_you_type checkbox
---
resources/default_tweaks.py | 7 +
src/calibre/gui2/__init__.py | 2 +
src/calibre/gui2/dialogs/config/__init__.py | 3 +
src/calibre/gui2/dialogs/config/config.ui | 22 +++-
src/calibre/gui2/tag_view.py | 138 ++++++++------------
src/calibre/library/database2.py | 4 +-
src/calibre/library/schema_upgrades.py | 29 ++--
7 files changed, 106 insertions(+), 99 deletions(-)
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index bda839b28f..2075391da4 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -71,3 +71,10 @@ gui_pubdate_display_format = 'MMM yyyy'
# order until the title is edited. Double-clicking on a title and hitting return
# without changing anything is sufficient to change the sort.
title_series_sorting = 'library_order'
+
+# How to render average rating in the tag browser.
+# There are two rendering methods available. The first is to show a partial
+# star, and the second is to show a partially filled rectangle. The first is
+# better looking, but uses more screen space than the second.
+# Values are 'star' or 'rectangle'
+render_avg_rating_using='star'
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 3063ef252d..e4d98c1e4b 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -101,6 +101,8 @@ def _config():
help=_('tag browser categories not to display'))
c.add_opt('gui_layout', choices=['wide', 'narrow'],
help=_('The layout of the user interface'), default='narrow')
+ c.add_opt('show_avg_rating', default=True,
+ help=_('Show the average rating per item indication in the tag browser'))
return ConfigProxy(c)
config = _config()
diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py
index b9c57b27ab..fa7bd8c2c5 100644
--- a/src/calibre/gui2/dialogs/config/__init__.py
+++ b/src/calibre/gui2/dialogs/config/__init__.py
@@ -481,6 +481,8 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit'])
self.device_detection_button.clicked.connect(self.debug_device_detection)
self.port.editingFinished.connect(self.check_port_value)
+ self.search_as_you_type.setChecked(config['search_as_you_type'])
+ self.show_avg_rating.setChecked(config['show_avg_rating'])
self.show_splash_screen.setChecked(gprefs.get('show_splash_screen',
True))
@@ -854,6 +856,7 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked()
config['search_as_you_type'] = self.search_as_you_type.isChecked()
+ config['show_avg_rating'] = self.show_avg_rating.isChecked()
config['get_social_metadata'] = self.opt_get_social_metadata.isChecked()
config['overwrite_author_title_metadata'] = self.opt_overwrite_author_title_metadata.isChecked()
config['enforce_cpu_limit'] = bool(self.opt_enforce_cpu_limit.isChecked())
diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui
index 917333a989..781b53f941 100644
--- a/src/calibre/gui2/dialogs/config/config.ui
+++ b/src/calibre/gui2/dialogs/config/config.ui
@@ -373,28 +373,38 @@
-
- Search as you type
+ Search as &you type
true
- -
+
-
+
+
+ Show &average ratings in the tags browser
+
+
+ true
+
+
+
+ -
Automatically send downloaded &news to ebook reader
- -
+
-
&Delete news from library when it is automatically sent to reader
- -
+
-
-
@@ -411,7 +421,7 @@
- -
+
-
Toolbar
@@ -459,7 +469,7 @@
- -
+
-
-
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 6ada763f80..9919ef97a2 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -9,17 +9,15 @@ Browsing book collection by tags.
from itertools import izip
from functools import partial
-from math import cos, sin, pi
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
QAbstractItemModel, QVariant, QModelIndex, QMenu, \
QPushButton, QWidget
-from PyQt4.Qt import QItemDelegate, QString, QPainterPath, QPen, QColor, \
- QLinearGradient, QBrush
+from PyQt4.Qt import QItemDelegate, QString, QPen, QColor, QLinearGradient, QBrush
from calibre.gui2 import config, NONE
-from calibre.utils.config import prefs
+from calibre.utils.config import prefs, tweaks
from calibre.library.field_metadata import TagsIcons
from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
@@ -31,84 +29,59 @@ class TagDelegate(QItemDelegate):
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
self._parent = parent
+ self.icon = QIcon(I('star.png'))
def paint(self, painter, option, index):
-
- def draw_rating(rect, rating):
- COLOR = QColor("blue")
- if rating is None:
- return 0
- painter.save()
- painter.translate(r.left(), r.top())
- factor = r.height()/100.
-# Try the star
-# star_path = QPainterPath()
-# star_path.moveTo(90, 50)
-# for i in range(1, 5):
-# star_path.lineTo(50 + 40 * cos(0.8 * i * pi), \
-# 50 + 40 * sin(0.8 * i * pi))
-# star_path.closeSubpath()
-# star_path.setFillRule(Qt.WindingFill)
-# pen = QPen(COLOR, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
-# gradient = QLinearGradient(0, 0, 0, 100)
-# gradient.setColorAt(0.0, COLOR)
-# gradient.setColorAt(1.0, COLOR)
-# painter.setBrush(QBrush(gradient))
-# painter.setClipRect(0, 0, int(r.height() * (rating/5.0)), r.height())
-# painter.scale(factor, factor)
-# painter.translate(50.0, 50.0)
-# painter.rotate(-20)
-# painter.translate(-50.0, -50.0)
-# painter.drawPath(star_path)
-# painter.restore()
-# return r.height()
-
-# Try a circle
-# gradient = QLinearGradient(0, 0, 0, 100)
-# gradient.setColorAt(0.0, COLOR)
-# gradient.setColorAt(1.0, COLOR)
-# painter.setBrush(QBrush(gradient))
-# painter.setClipRect(0, 0, int(r.height() * (rating/5.0)), r.height())
-# painter.scale(factor, factor)
-# painter.drawEllipse(0, 0, 100, 100)
-# painter.restore()
-# return r.height()
-
-# Try a rectangle
- width = 20
- height = 80
- left_offset = 5
- top_offset = 10
- if rating > 0.0:
- pen = QPen(COLOR, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
- painter.setPen(pen)
- painter.scale(factor, factor)
- painter.drawRect(left_offset, top_offset, width, height)
- fill_height = height*(rating/5.0)
- gradient = QLinearGradient(0, 0, 0, 100)
- gradient.setColorAt(0.0, COLOR)
- gradient.setColorAt(1.0, COLOR)
- painter.setBrush(QBrush(gradient))
- painter.drawRect(left_offset, top_offset+(height-fill_height),
- width, fill_height)
- painter.restore()
- return int ((width+left_offset*2) * factor)
-
item = index.internalPointer()
- if item.type == TagTreeItem.TAG:
- r = option.rect
- # Paint the decoration icon
- icon = self._parent.model().data(index, Qt.DecorationRole).toPyObject()
- icon.paint(painter, r, Qt.AlignLeft)
- # Paint the rating, if any
- r.setLeft(r.left()+r.height()+5)
- text_start = draw_rating(r, item.tag.avg)
- # Paint the text
- r.setLeft(r.left() + text_start+5)
- painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
- QString('[%d] %s'%(item.tag.count, item.tag.name)))
- else:
+ if item.type != TagTreeItem.TAG:
QItemDelegate.paint(self, painter, option, index)
+ return
+ r = option.rect
+ # Paint the decoration icon
+ icon = self._parent.model().data(index, Qt.DecorationRole).toPyObject()
+ icon.paint(painter, r, Qt.AlignLeft)
+
+ # Paint the rating, if any. The decoration icon is assumed to be square,
+ # filling the row top to bottom. The three is arbitrary, there to
+ # provide a little space between the icon and what follows
+ r.setLeft(r.left()+r.height()+3)
+ rating = item.tag.avg_rating
+ if config['show_avg_rating'] and item.tag.avg_rating is not None:
+ painter.save()
+ if tweaks['render_avg_rating_using'] == 'star':
+ painter.setClipRect(r.left(), r.top(),
+ int(r.height()*(rating/5.0)), r.height())
+ self.icon.paint(painter, r, Qt.AlignLeft | Qt.AlignVCenter)
+ r.setLeft(r.left() + r.height())
+ else:
+ painter.translate(r.left(), r.top())
+ # Compute factor so sizes can be expressed in percentages of the
+ # box defined by the row height
+ factor = r.height()/100.
+ width = 20
+ height = 80
+ left_offset = 5
+ top_offset = 10
+ if r > 0.0:
+ color = QColor(100, 100, 255) #medium blue, less glare
+ pen = QPen(color, 5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
+ painter.setPen(pen)
+ painter.scale(factor, factor)
+ painter.drawRect(left_offset, top_offset, width, height)
+ fill_height = height*(rating/5.0)
+ gradient = QLinearGradient(0, 0, 0, 100)
+ gradient.setColorAt(0.0, color)
+ gradient.setColorAt(1.0, color)
+ painter.setBrush(QBrush(gradient))
+ painter.drawRect(left_offset, top_offset+(height-fill_height),
+ width, fill_height)
+ # The '3' is arbitrary, there because we need a little space
+ # between the rectangle and the text.
+ r.setLeft(r.left() + ((width+left_offset*2)*factor) + 3)
+ painter.restore()
+ # Paint the text
+ painter.drawText(r, Qt.AlignLeft|Qt.AlignVCenter,
+ QString('[%d] %s'%(item.tag.count, item.tag.name)))
class TagsView(QTreeView): # {{{
@@ -386,10 +359,11 @@ class TagTreeItem(object): # {{{
if self.tag.count == 0:
return QVariant('%s'%(self.tag.name))
else:
- if self.tag.avg is None:
+ if self.tag.avg_rating is None:
return QVariant('[%d] %s'%(self.tag.count, self.tag.name))
else:
- return QVariant('[%d][%3.1f] %s'%(self.tag.count, self.tag.avg, self.tag.name))
+ return QVariant('[%d][%3.1f] %s'%(self.tag.count,
+ self.tag.avg_rating, self.tag.name))
if role == Qt.EditRole:
return QVariant(self.tag.name)
if role == Qt.DecorationRole:
@@ -453,7 +427,7 @@ class TagsModel(QAbstractItemModel): # {{{
if r not in self.categories_with_ratings and \
not self.db.field_metadata[r]['is_custom'] and \
not self.db.field_metadata[r]['kind'] == 'user':
- tag.avg = None
+ tag.avg_rating = None
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
def set_search_restriction(self, s):
@@ -519,7 +493,7 @@ class TagsModel(QAbstractItemModel): # {{{
if r not in self.categories_with_ratings and \
not self.db.field_metadata[r]['is_custom'] and \
not self.db.field_metadata[r]['kind'] == 'user':
- tag.avg = None
+ tag.avg_rating = None
tag.state = state_map.get(tag.name, 0)
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
self.endInsertRows()
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 04acca913c..939ab92f38 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -61,7 +61,7 @@ class Tag(object):
self.id = id
self.count = count
self.state = state
- self.avg = avg/2.0 if avg is not None else 0
+ self.avg_rating = avg/2.0 if avg is not None else 0
self.tooltip = tooltip
self.icon = icon
@@ -126,8 +126,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.connect()
self.is_case_sensitive = not iswindows and not isosx and \
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
- self.initialize_dynamic()
SchemaUpgrade.__init__(self)
+ self.initialize_dynamic()
def initialize_dynamic(self):
self.conn.executescript(u'''
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index 0660a8b136..870254f999 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -355,13 +355,24 @@ class SchemaUpgrade(object):
'''.format(lt=link_table_name, table=table_name)
self.conn.executescript(script)
- for field in self.field_metadata.itervalues():
- if field['is_category'] and not field['is_custom'] and 'link_column' in field:
- create_std_tag_browser_view(field['table'], field['link_column'],
- field['column'])
+ STANDARD_TAG_BROWSER_TABLES = [
+ ('authors', 'author', 'name'),
+ ('publishers', 'publisher', 'name'),
+ ('ratings', 'rating', 'rating'),
+ ('series', 'series', 'name'),
+ ('tags', 'tag', 'name'),
+ ]
+ for table, column, view_column in STANDARD_TAG_BROWSER_TABLES:
+ create_std_tag_browser_view(table, column, view_column)
- for field in self.field_metadata.itervalues():
- if field['is_category'] and field['is_custom']:
- link_table_name = 'books_custom_column_%d_link'%field['colnum']
- print 'try to upgrade cust col', field['table'], link_table_name
- create_cust_tag_browser_view(field['table'], link_table_name)
+ db_tables = self.conn.get('''SELECT name FROM sqlite_master
+ WHERE type='table'
+ ORDER BY name''');
+ tables = []
+ for (table,) in db_tables:
+ tables.append(table)
+ for table in tables:
+ link_table = 'books_%s_link'%table
+ if table.startswith('custom_column_') and link_table in tables:
+ print table
+ create_cust_tag_browser_view(table, link_table)
\ No newline at end of file
From 669dd8024c6eb1f4e5b2961747565c393b140743 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Sun, 13 Jun 2010 22:58:46 +0100
Subject: [PATCH 06/13] Author_sort in author table changes
---
src/calibre/gui2/convert/metadata.py | 4 +-
src/calibre/gui2/device.py | 4 +-
src/calibre/gui2/dialogs/metadata_bulk.py | 7 +-
src/calibre/gui2/dialogs/metadata_single.py | 4 +-
src/calibre/gui2/dialogs/sort_field_dialog.py | 16 ++++
src/calibre/gui2/dialogs/sort_field_dialog.ui | 83 +++++++++++++++++
src/calibre/gui2/tag_view.py | 21 +++++
src/calibre/library/database2.py | 93 +++++++++++++------
src/calibre/library/field_metadata.py | 3 +-
src/calibre/library/schema_upgrades.py | 61 ++++++++----
src/calibre/library/sqlite.py | 4 +-
11 files changed, 242 insertions(+), 58 deletions(-)
create mode 100644 src/calibre/gui2/dialogs/sort_field_dialog.py
create mode 100644 src/calibre/gui2/dialogs/sort_field_dialog.ui
diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py
index 2026f1cee5..3ddd5674bb 100644
--- a/src/calibre/gui2/convert/metadata.py
+++ b/src/calibre/gui2/convert/metadata.py
@@ -13,7 +13,7 @@ from PyQt4.Qt import QPixmap, SIGNAL
from calibre.gui2 import choose_images, error_dialog
from calibre.gui2.convert.metadata_ui import Ui_Form
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
- MetaInformation, authors_to_sort_string
+ MetaInformation
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.convert import Widget
@@ -57,7 +57,7 @@ class MetadataWidget(Widget, Ui_Form):
au = unicode(self.author.currentText())
au = re.sub(r'\s+et al\.$', '', au)
authors = string_to_authors(au)
- self.author_sort.setText(authors_to_sort_string(authors))
+ self.author_sort.setText(self.db.author_sort_from_authors(authors))
def initialize_metadata_options(self):
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index cf54e6c1f3..c8eb4c2403 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -23,7 +23,7 @@ from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \
question_dialog, info_dialog, choose_dir
-from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
+from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints
from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError
@@ -1409,7 +1409,7 @@ class DeviceMixin(object): # {{{
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort and book.authors:
- book.author_sort = authors_to_sort_string(book.authors)
+ book.author_sort = self.db.author_sort_from_authors(book.authors)
resend_metadata = True
if resend_metadata:
diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py
index eca7fe9c15..8b27ff1999 100644
--- a/src/calibre/gui2/dialogs/metadata_bulk.py
+++ b/src/calibre/gui2/dialogs/metadata_bulk.py
@@ -8,7 +8,7 @@ from PyQt4.QtGui import QDialog, QGridLayout
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
-from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string, \
+from calibre.ebooks.metadata import string_to_authors, \
authors_to_string
from calibre.gui2.custom_column_widgets import populate_bulk_metadata_page
@@ -110,10 +110,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
au = string_to_authors(au)
self.db.set_authors(id, au, notify=False)
if self.auto_author_sort.isChecked():
- aut = self.db.authors(id, index_is_id=True)
- aut = aut if aut else ''
- aut = [a.strip().replace('|', ',') for a in aut.strip().split(',')]
- x = authors_to_sort_string(aut)
+ x = self.db.author_sort_from_book(id, index_is_id=True)
if x:
self.db.set_author_sort(id, x, notify=False)
aus = unicode(self.author_sort.text())
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 0241e1b542..1543df458e 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -23,7 +23,7 @@ from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.gui2.widgets import ProgressIndicator
from calibre.ebooks import BOOK_EXTENSIONS
-from calibre.ebooks.metadata import authors_to_sort_string, string_to_authors, \
+from calibre.ebooks.metadata import string_to_authors, \
authors_to_string, check_isbn
from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre import islinux, isfreebsd
@@ -459,7 +459,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
au = unicode(self.authors.text())
au = re.sub(r'\s+et al\.$', '', au)
authors = string_to_authors(au)
- self.author_sort.setText(authors_to_sort_string(authors))
+ self.author_sort.setText(self.db.author_sort_from_authors(authors))
def swap_title_author(self):
title = self.title.text()
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.py b/src/calibre/gui2/dialogs/sort_field_dialog.py
new file mode 100644
index 0000000000..d1c6d45ed3
--- /dev/null
+++ b/src/calibre/gui2/dialogs/sort_field_dialog.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
+__docformat__ = 'restructuredtext en'
+__license__ = 'GPL v3'
+
+from PyQt4.Qt import QDialog
+from calibre.gui2.dialogs.sort_field_dialog_ui import Ui_SortFieldDialog
+
+class SortFieldDialog(QDialog, Ui_SortFieldDialog):
+
+ def __init__(self, parent, text):
+ QDialog.__init__(self, parent)
+ Ui_SortFieldDialog.__init__(self)
+ self.setupUi(self)
+ if text is not None:
+ self.textbox.setText(text)
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.ui b/src/calibre/gui2/dialogs/sort_field_dialog.ui
new file mode 100644
index 0000000000..3fc386d1ef
--- /dev/null
+++ b/src/calibre/gui2/dialogs/sort_field_dialog.ui
@@ -0,0 +1,83 @@
+
+
+ SortFieldDialog
+
+
+
+ 0
+ 0
+ 334
+ 135
+
+
+
+
+ 0
+ 0
+
+
+
+ Edit sort field
+
+
+
+
+ 10
+ 10
+ 311
+ 111
+
+
+
+
-
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ SortFieldDialog
+ accept()
+
+
+ 229
+ 211
+
+
+ 157
+ 234
+
+
+
+
+ buttonBox
+ rejected()
+ SortFieldDialog
+ reject()
+
+
+ 297
+ 217
+
+
+ 286
+ 234
+
+
+
+
+
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 9919ef97a2..bae81c79cd 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -23,6 +23,7 @@ from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
+from calibre.gui2.dialogs.sort_field_dialog import SortFieldDialog
class TagDelegate(QItemDelegate):
@@ -90,6 +91,7 @@ class TagsView(QTreeView): # {{{
user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
+ author_sort_edit = pyqtSignal(object, object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
@@ -173,6 +175,9 @@ class TagsView(QTreeView): # {{{
if action == 'manage_searches':
self.saved_search_edit.emit(category)
return
+ if action == 'edit_author_sort':
+ self.author_sort_edit.emit(self, category, index)
+ return
if action == 'hide':
self.hidden_categories.add(category)
elif action == 'show':
@@ -193,6 +198,8 @@ class TagsView(QTreeView): # {{{
if item.type == TagTreeItem.TAG:
tag_item = item
tag_name = item.tag.name
+ tag_id = item.tag.id
+ tag_sort = item.tag.sort
item = item.parent
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
@@ -211,6 +218,10 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(_('Rename') + " '" + tag_name + "'",
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
+ if key == 'authors':
+ self.context_menu.addAction(_('Edit sort for') + " '" + tag_name + "'",
+ partial(self.context_menu_handler, action='edit_author_sort',
+ category=tag_sort, index=tag_id))
self.context_menu.addSeparator()
# Hide/Show/Restore categories
self.context_menu.addAction(_('Hide category %s') % category,
@@ -684,6 +695,7 @@ class TagBrowserMixin(object): # {{{
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.author_sort_edit.connect(self.do_author_sort_edit)
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.edit_categories.clicked.connect(lambda x:
@@ -713,6 +725,15 @@ class TagBrowserMixin(object): # {{{
self.saved_search.clear_to_help()
self.search.clear_to_help()
+ def do_author_sort_edit(self, parent, text, id):
+ editor = SortFieldDialog(parent, text)
+ d = editor.exec_()
+ if d:
+ print editor.textbox.text()
+ self.library_view.model().db.set_sort_field_for_author \
+ (id, unicode(editor.textbox.text()))
+ self.tags_view.recount()
+
# }}}
class TagBrowserWidget(QWidget): # {{{
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 939ab92f38..144b66b5e4 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -12,7 +12,7 @@ from math import floor
from PyQt4.QtGui import QImage
-from calibre.ebooks.metadata import title_sort
+from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.library.database import LibraryDatabase
from calibre.library.field_metadata import FieldMetadata, TagsIcons
from calibre.library.schema_upgrades import SchemaUpgrade
@@ -20,7 +20,7 @@ from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
- MetaInformation, authors_to_sort_string
+ MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
@@ -56,12 +56,14 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
class Tag(object):
- def __init__(self, name, id=None, count=0, state=0, avg=0, tooltip=None, icon=None):
+ def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
+ tooltip=None, icon=None):
self.name = name
self.id = id
self.count = count
self.state = state
self.avg_rating = avg/2.0 if avg is not None else 0
+ self.sort = sort
self.tooltip = tooltip
self.icon = icon
@@ -135,7 +137,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
id,
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,
- (0) as avg_rating
+ (0) as avg_rating,
+ (null) as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -147,7 +150,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
id,
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,
- (0) as avg_rating
+ (0) as avg_rating,
+ (null) as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -425,6 +429,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
+ mi.authors_sort_strings = self.authors_sort_strings(idx, index_is_id)
mi.comments = self.comments(idx, index_is_id=index_is_id)
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
@@ -701,12 +706,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
continue
cn = cat['column']
if ids is None:
- query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_{1}'.format(cn, tn)
+ query = '''SELECT id, {0}, count, avg_rating, sort
+ FROM tag_browser_{1}'''.format(cn, tn)
else:
- query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_filtered_{1}'.format(cn, tn)
+ query = '''SELECT id, {0}, count, avg_rating
+ FROM tag_browser_filtered_{1}'''.format(cn, tn)
if sort_on_count:
query += ' ORDER BY count DESC'
else:
+ if 'category_sort' in cat:
+ cn = cat['category_sort']
query += ' ORDER BY {0} ASC'.format(cn)
data = self.conn.get(query)
@@ -736,7 +745,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formatter = (lambda x:unicode(x))
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
- avg=r[3], icon=icon, tooltip=tooltip)
+ avg=r[3], sort=r[4],
+ icon=icon, tooltip=tooltip)
for r in data if item_not_zero_func(r)]
if category == 'series' and not sort_on_count:
if tweaks['title_series_sorting'] == 'library_order':
@@ -912,6 +922,38 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.set_path(id, True)
self.notify('metadata', [id])
+ # Given a book, return the list of author sort strings for the book's authors
+ def authors_sort_strings(self, id, index_is_id=False):
+ id = id if index_is_id else self.id(id)
+ aut_strings = self.conn.get('''
+ SELECT sort
+ FROM authors, books_authors_link as bl
+ WHERE bl.book=? and authors.id=bl.author
+ ORDER BY bl.id''', (id,))
+ result = []
+ for (sort,) in aut_strings:
+ result.append(sort)
+ return result
+
+ # Given a book, return the author_sort string for authors of the book
+ def author_sort_from_book(self, id, index_is_id=False):
+ auts = self.authors_sort_strings(id, index_is_id)
+ return ' & '.join(auts).replace('|', ',')
+
+ # Given a list of authors, return the author_sort string for the authors,
+ # preferring the author sort associated with the author over the computed
+ # string
+ def author_sort_from_authors(self, authors):
+ result = []
+ for aut in authors:
+ aut = aut.replace(',', '|')
+ r = self.conn.get('SELECT sort FROM authors WHERE name=?', (aut,), all=False)
+ if r is None:
+ result.append(author_to_author_sort(aut))
+ else:
+ result.append(r)
+ return ' & '.join(result).replace('|', ',')
+
def set_authors(self, id, authors, notify=True):
'''
`authors`: A list of authors.
@@ -938,7 +980,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
(id, aid))
except IntegrityError: # Sometimes books specify the same author twice in their metadata
pass
- ss = authors_to_sort_string(authors)
+ self.conn.commit()
+ ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
self.conn.commit()
@@ -1117,7 +1160,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.commit()
# There is no editor for author, so we do not need get_authors_with_ids or
- # delete_author_using_id.
+ # delete_author_using_id. However, we can change the author's sort field, so
+ # we provide that setter
+
+ def set_sort_field_for_author(self, old_id, new_sort):
+ self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
+ (new_sort, old_id))
+ self.conn.commit()
def rename_author(self, old_id, new_name):
# Make sure that any commas in new_name are changed to '|'!
@@ -1187,22 +1236,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# 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
- ORDER BY bl.id
- ''' , (book_id,))
- # unpack the double-list structure
- for i,aut in enumerate(authors):
- authors[i] = aut[0]
- ss = authors_to_sort_string(authors)
- # Change the '|'s to ','
- ss = ss.replace('|', ',')
- self.conn.execute('''UPDATE books
- SET author_sort=?
- WHERE id=?''', (ss, book_id))
- self.conn.commit()
+ ss = self.author_sort_from_book(book_id, index_is_id=True)
+ self.set_author_sort(book_id, ss)
# the caller will do a general refresh, so we don't need to
# do one here
@@ -1439,7 +1474,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if not add_duplicates and self.has_book(mi):
return None
series_index = 1.0 if mi.series_index is None else mi.series_index
- aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
+ aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
title = mi.title
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
@@ -1479,7 +1514,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
duplicates.append((path, format, mi))
continue
series_index = 1.0 if mi.series_index is None else mi.series_index
- aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
+ aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
title = mi.title
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
@@ -1518,7 +1553,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.title = _('Unknown')
if not mi.authors:
mi.authors = [_('Unknown')]
- aus = mi.author_sort if mi.author_sort else authors_to_sort_string(mi.authors)
+ aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
title = mi.title if isinstance(mi.title, unicode) else \
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 82e4edfdf2..535893b24c 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -72,7 +72,8 @@ class FieldMetadata(dict):
'name':_('Authors'),
'search_terms':['authors', 'author'],
'is_custom':False,
- 'is_category':True}),
+ 'is_category':True,
+ 'category_sort':'sort'}),
('series', {'table':'series',
'column':'name',
'link_column':'series',
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index 870254f999..5763cbad70 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -295,7 +295,8 @@ class SchemaUpgrade(object):
def upgrade_version_11(self):
'Add average rating to tag browser views'
- def create_std_tag_browser_view(table_name, column_name, view_column_name):
+ def create_std_tag_browser_view(table_name, column_name,
+ view_column_name, sort_column_name):
script = ('''
DROP VIEW IF EXISTS tag_browser_{tn};
CREATE VIEW tag_browser_{tn} AS SELECT
@@ -305,22 +306,25 @@ class SchemaUpgrade(object):
(SELECT AVG(ratings.rating)
FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
WHERE tl.{cn}={tn}.id and bl.book=tl.book and
- ratings.id = bl.rating and ratings.rating <> 0) avg_rating
+ ratings.id = bl.rating and ratings.rating <> 0) avg_rating,
+ {scn} as sort
FROM {tn};
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
id,
- {vcn},
+ {vcn} as sort,
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
{cn}={tn}.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
WHERE tl.{cn}={tn}.id and bl.book=tl.book and
ratings.id = bl.rating and ratings.rating <> 0 AND
- books_list_filter(bl.book)) avg_rating
+ books_list_filter(bl.book)) avg_rating,
+ {scn} as sort
FROM {tn};
- '''.format(tn=table_name, cn=column_name, vcn=view_column_name))
+ '''.format(tn=table_name, cn=column_name,
+ vcn=view_column_name, scn= sort_column_name))
self.conn.executescript(script)
def create_cust_tag_browser_view(table_name, link_table_name):
@@ -335,7 +339,8 @@ class SchemaUpgrade(object):
books_ratings_link as bl,
ratings as r
WHERE {lt}.value={table}.id and bl.book={lt}.book and
- r.id = bl.rating and r.rating <> 0) avg_rating
+ r.id = bl.rating and r.rating <> 0) avg_rating,
+ value as sort
FROM {table};
DROP VIEW IF EXISTS tag_browser_filtered_{table};
@@ -350,20 +355,21 @@ class SchemaUpgrade(object):
ratings as r
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
r.id = bl.rating AND r.rating <> 0 AND
- books_list_filter(bl.book)) avg_rating
+ books_list_filter(bl.book)) avg_rating,
+ value as sort
FROM {table};
'''.format(lt=link_table_name, table=table_name)
self.conn.executescript(script)
STANDARD_TAG_BROWSER_TABLES = [
- ('authors', 'author', 'name'),
- ('publishers', 'publisher', 'name'),
- ('ratings', 'rating', 'rating'),
- ('series', 'series', 'name'),
- ('tags', 'tag', 'name'),
+ ('authors', 'author', 'name', 'sort'),
+ ('publishers', 'publisher', 'name', 'name'),
+ ('ratings', 'rating', 'rating', 'rating'),
+ ('series', 'series', 'name', 'name'),
+ ('tags', 'tag', 'name', 'name'),
]
- for table, column, view_column in STANDARD_TAG_BROWSER_TABLES:
- create_std_tag_browser_view(table, column, view_column)
+ for table, column, view_column, sort_column in STANDARD_TAG_BROWSER_TABLES:
+ create_std_tag_browser_view(table, column, view_column, sort_column)
db_tables = self.conn.get('''SELECT name FROM sqlite_master
WHERE type='table'
@@ -374,5 +380,28 @@ class SchemaUpgrade(object):
for table in tables:
link_table = 'books_%s_link'%table
if table.startswith('custom_column_') and link_table in tables:
- print table
- create_cust_tag_browser_view(table, link_table)
\ No newline at end of file
+ create_cust_tag_browser_view(table, link_table)
+
+ from calibre.ebooks.metadata import author_to_author_sort
+
+ aut = self.conn.get('SELECT id, name FROM authors');
+ records = []
+ for (id, author) in aut:
+ records.append((id, author.replace('|', ',')))
+ for id,author in records:
+ self.conn.execute('UPDATE authors SET sort=? WHERE id=?',
+ (author_to_author_sort(author.replace('|', ',')).strip(), id))
+ self.conn.commit()
+ self.conn.executescript('''
+ CREATE TRIGGER author_insert_trg
+ AFTER INSERT ON authors
+ BEGIN
+ UPDATE authors SET sort=author_to_author_sort(NEW.name) WHERE id=NEW.id;
+ END;
+ CREATE TRIGGER author_update_trg
+ BEFORE UPDATE ON authors
+ BEGIN
+ UPDATE authors SET sort=author_to_author_sort(NEW.name)
+ WHERE id=NEW.id and name <> NEW.name;
+ END;
+ ''')
\ No newline at end of file
diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py
index adf6691671..9aab71ab79 100644
--- a/src/calibre/library/sqlite.py
+++ b/src/calibre/library/sqlite.py
@@ -14,7 +14,7 @@ from Queue import Queue
from threading import RLock
from datetime import datetime
-from calibre.ebooks.metadata import title_sort
+from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, isoformat
@@ -120,6 +120,8 @@ class DBThread(Thread):
self.conn.create_function('title_sort', 1, title_sort)
else:
self.conn.create_function('title_sort', 1, lambda x:x)
+ self.conn.create_function('author_to_author_sort', 1,
+ lambda x: author_to_author_sort(x.replace('|', ',')))
self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4()))
# Dummy functions for dynamically created filters
self.conn.create_function('books_list_filter', 1, lambda x: 1)
From 34312daed3cb04544a48bdc0483495c95ec1752f Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 10:30:13 +0100
Subject: [PATCH 07/13] Improvements to the new tag browser views.
---
src/calibre/library/database2.py | 16 ++++------------
src/calibre/library/field_metadata.py | 18 +++++++++++++-----
src/calibre/library/schema_upgrades.py | 14 +++++---------
src/calibre/library/sqlite.py | 6 +++---
4 files changed, 25 insertions(+), 29 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 144b66b5e4..a4e8b4ff77 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -138,7 +138,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,
(0) as avg_rating,
- (null) as sort
+ name as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -151,7 +151,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,
(0) as avg_rating,
- (null) as sort
+ name as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@@ -714,9 +714,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if sort_on_count:
query += ' ORDER BY count DESC'
else:
- if 'category_sort' in cat:
- cn = cat['category_sort']
- query += ' ORDER BY {0} ASC'.format(cn)
+ query += ' ORDER BY sort ASC'
data = self.conn.get(query)
# icon_map is not None if get_categories is to store an icon and
@@ -734,6 +732,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
datatype = cat['datatype']
if datatype == 'rating':
+ # eliminate the zero ratings line as well as count == 0
item_not_zero_func = (lambda x: x[1] > 0 and x[2] > 0)
formatter = (lambda x:u'\u2605'*int(round(x/2.)))
elif category == 'authors':
@@ -748,13 +747,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
avg=r[3], sort=r[4],
icon=icon, tooltip=tooltip)
for r in data if item_not_zero_func(r)]
- if category == 'series' and not sort_on_count:
- if tweaks['title_series_sorting'] == 'library_order':
- ts = lambda x: title_sort(x)
- else:
- ts = lambda x:x
- categories[category].sort(cmp=lambda x,y:cmp(ts(x.name).lower(),
- ts(y.name).lower()))
# We delayed computing the standard formats category because it does not
# use a view, but is computed dynamically
diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py
index 535893b24c..8cb5c9bdad 100644
--- a/src/calibre/library/field_metadata.py
+++ b/src/calibre/library/field_metadata.py
@@ -44,9 +44,12 @@ class FieldMetadata(dict):
is_category: is a tag browser category. If true, then:
table: name of the db table used to construct item list
column: name of the column in the normalized table to join on
- link_column: name of the column in the connection table to join on
+ link_column: name of the column in the connection table to join on. This
+ key should not be present if there is no link table
+ category_sort: the field in the normalized table to sort on. This
+ key must be present if is_category is True
If these are None, then the category constructor must know how
- to build the item list (e.g., formats).
+ to build the item list (e.g., formats, news).
The order below is the order that the categories will
appear in the tags pane.
@@ -66,17 +69,18 @@ class FieldMetadata(dict):
('authors', {'table':'authors',
'column':'name',
'link_column':'author',
+ 'category_sort':'sort',
'datatype':'text',
'is_multiple':',',
'kind':'field',
'name':_('Authors'),
'search_terms':['authors', 'author'],
'is_custom':False,
- 'is_category':True,
- 'category_sort':'sort'}),
+ 'is_category':True}),
('series', {'table':'series',
'column':'name',
'link_column':'series',
+ 'category_sort':'(title_sort(name))',
'datatype':'text',
'is_multiple':None,
'kind':'field',
@@ -96,6 +100,7 @@ class FieldMetadata(dict):
('publisher', {'table':'publishers',
'column':'name',
'link_column':'publisher',
+ 'category_sort':'name',
'datatype':'text',
'is_multiple':None,
'kind':'field',
@@ -106,6 +111,7 @@ class FieldMetadata(dict):
('rating', {'table':'ratings',
'column':'rating',
'link_column':'rating',
+ 'category_sort':'rating',
'datatype':'rating',
'is_multiple':None,
'kind':'field',
@@ -115,6 +121,7 @@ class FieldMetadata(dict):
'is_category':True}),
('news', {'table':'news',
'column':'name',
+ 'category_sort':'name',
'datatype':None,
'is_multiple':None,
'kind':'category',
@@ -125,6 +132,7 @@ class FieldMetadata(dict):
('tags', {'table':'tags',
'column':'name',
'link_column': 'tag',
+ 'category_sort':'name',
'datatype':'text',
'is_multiple':',',
'kind':'field',
@@ -375,7 +383,7 @@ class FieldMetadata(dict):
'search_terms':[key], 'label':label,
'colnum':colnum, 'display':display,
'is_custom':True, 'is_category':is_category,
- 'link_column':'value',
+ 'link_column':'value','category_sort':'value',
'is_editable': is_editable,}
self._add_search_terms_to_map(key, [key])
self.custom_label_to_key_map[label] = key
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index 5763cbad70..fd3762cc94 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -361,15 +361,11 @@ class SchemaUpgrade(object):
'''.format(lt=link_table_name, table=table_name)
self.conn.executescript(script)
- STANDARD_TAG_BROWSER_TABLES = [
- ('authors', 'author', 'name', 'sort'),
- ('publishers', 'publisher', 'name', 'name'),
- ('ratings', 'rating', 'rating', 'rating'),
- ('series', 'series', 'name', 'name'),
- ('tags', 'tag', 'name', 'name'),
- ]
- for table, column, view_column, sort_column in STANDARD_TAG_BROWSER_TABLES:
- create_std_tag_browser_view(table, column, view_column, sort_column)
+ for field in self.field_metadata.itervalues():
+ if field['is_category'] and not field['is_custom'] and 'link_column' in field:
+ print field['table']
+ create_std_tag_browser_view(field['table'], field['link_column'],
+ field['column'], field['category_sort'])
db_tables = self.conn.get('''SELECT name FROM sqlite_master
WHERE type='table'
diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py
index 9aab71ab79..7e0458fba4 100644
--- a/src/calibre/library/sqlite.py
+++ b/src/calibre/library/sqlite.py
@@ -116,10 +116,10 @@ class DBThread(Thread):
self.conn.create_aggregate('concat', 1, Concatenate)
self.conn.create_aggregate('sortconcat', 2, SortedConcatenate)
self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate)
- if tweaks['title_series_sorting'] == 'library_order':
- self.conn.create_function('title_sort', 1, title_sort)
- else:
+ if tweaks['title_series_sorting'] == 'strictly_alphabetic':
self.conn.create_function('title_sort', 1, lambda x:x)
+ else:
+ self.conn.create_function('title_sort', 1, title_sort)
self.conn.create_function('author_to_author_sort', 1,
lambda x: author_to_author_sort(x.replace('|', ',')))
self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4()))
From 5dfbc21472e5555b4c4aa3292b2720f67f595b96 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 10:40:22 +0100
Subject: [PATCH 08/13] Fix up conflicts in preferences ordering on the
interface screen
---
src/calibre/gui2/dialogs/config/config.ui | 30 +++++++++++------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui
index b66e16a487..ba92c0d301 100644
--- a/src/calibre/gui2/dialogs/config/config.ui
+++ b/src/calibre/gui2/dialogs/config/config.ui
@@ -370,16 +370,6 @@
- -
-
-
- Search as you type
-
-
- true
-
-
-
-
@@ -390,21 +380,31 @@
- -
+
-
+
+
+ Search as you type
+
+
+ true
+
+
+
+ -
Automatically send downloaded &news to ebook reader
- -
+
-
&Delete news from library when it is automatically sent to reader
- -
+
-
-
@@ -421,7 +421,7 @@
- -
+
-
Toolbar
@@ -469,7 +469,7 @@
- -
+
-
-
From db507d4b13c53cfa334068ddf744f015d1cfbca2 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 12:29:32 +0100
Subject: [PATCH 09/13] First iteration at an authors management dialog
---
src/calibre/gui2/dialogs/sort_field_dialog.py | 62 +++++++++++++++++--
src/calibre/gui2/dialogs/sort_field_dialog.ui | 14 +++--
src/calibre/gui2/tag_view.py | 11 ++--
src/calibre/library/database2.py | 9 +--
4 files changed, 79 insertions(+), 17 deletions(-)
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.py b/src/calibre/gui2/dialogs/sort_field_dialog.py
index d1c6d45ed3..a73d0d8cf4 100644
--- a/src/calibre/gui2/dialogs/sort_field_dialog.py
+++ b/src/calibre/gui2/dialogs/sort_field_dialog.py
@@ -3,14 +3,68 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__license__ = 'GPL v3'
-from PyQt4.Qt import QDialog
+from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView
+
+from calibre.ebooks.metadata import author_to_author_sort
from calibre.gui2.dialogs.sort_field_dialog_ui import Ui_SortFieldDialog
+class tableItem(QTableWidgetItem):
+ def __ge__(self, other):
+ return unicode(self.text()).lower() >= unicode(other.text()).lower()
+
+ def __lt__(self, other):
+ return unicode(self.text()).lower() < unicode(other.text()).lower()
+
class SortFieldDialog(QDialog, Ui_SortFieldDialog):
- def __init__(self, parent, text):
+ def __init__(self, parent, db, id_to_select):
QDialog.__init__(self, parent)
Ui_SortFieldDialog.__init__(self)
self.setupUi(self)
- if text is not None:
- self.textbox.setText(text)
+
+ self.buttonBox.accepted.connect(self.accepted)
+ self.table.cellChanged.connect(self.cell_changed)
+
+ self.table.setSelectionMode(QAbstractItemView.SingleSelection)
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort')])
+
+ self.authors = {}
+ auts = db.get_authors_with_ids()
+ self.table.setRowCount(len(auts))
+ select_item = None
+ for row, (id, author, sort) in enumerate(auts):
+ author = author.replace('|', ',')
+ self.authors[id] = (author, sort)
+ aut = tableItem(author)
+ aut.setData(Qt.UserRole, id)
+ sort = tableItem(sort)
+ self.table.setItem(row, 0, aut)
+ self.table.setItem(row, 1, sort)
+ if id == id_to_select:
+ select_item = aut
+
+ if select_item is not None:
+ self.table.setCurrentItem(select_item)
+ self.table.resizeColumnsToContents()
+ self.table.setSortingEnabled(True)
+ self.table.sortByColumn(1, Qt.AscendingOrder)
+
+ def accepted(self):
+ print 'accepted!'
+ self.result = []
+ for row in range(0,self.table.rowCount()):
+ id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
+ aut = unicode(self.table.item(row, 0).text())
+ sort = unicode(self.table.item(row, 1).text())
+ print id, aut, sort
+ orig_aut,orig_sort = self.authors[id]
+ if orig_aut != aut or orig_sort != sort:
+ self.result.append((id, orig_aut, aut, sort))
+
+ def cell_changed(self, row, col):
+ if col == 0:
+ aut = unicode(self.table.item(row, 0).text())
+ c = self.table.item(row, 1)
+ if c is not None:
+ c.setText(author_to_author_sort(aut))
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.ui b/src/calibre/gui2/dialogs/sort_field_dialog.ui
index 3fc386d1ef..bc6ffae4fc 100644
--- a/src/calibre/gui2/dialogs/sort_field_dialog.ui
+++ b/src/calibre/gui2/dialogs/sort_field_dialog.ui
@@ -6,8 +6,8 @@
0
0
- 334
- 135
+ 518
+ 262
@@ -24,13 +24,17 @@
10
10
- 311
- 111
+ 501
+ 231
-
-
+
+
+ 0
+
+
-
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index bae81c79cd..27d73a0af8 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -726,12 +726,15 @@ class TagBrowserMixin(object): # {{{
self.search.clear_to_help()
def do_author_sort_edit(self, parent, text, id):
- editor = SortFieldDialog(parent, text)
+ editor = SortFieldDialog(parent, self.library_view.model().db, id)
d = editor.exec_()
if d:
- print editor.textbox.text()
- self.library_view.model().db.set_sort_field_for_author \
- (id, unicode(editor.textbox.text()))
+ print editor.result
+ for (id, old_author, new_author, new_sort) in editor.result:
+ if old_author != new_author:
+ self.library_view.model().db.rename_author(id, new_author)
+ self.library_view.model().db.set_sort_field_for_author \
+ (id, unicode(new_sort))
self.tags_view.recount()
# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index a4e8b4ff77..794474f821 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1113,7 +1113,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
index = index + 1
self.conn.commit()
-
def delete_series_using_id(self, id):
books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,))
self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,))
@@ -1151,9 +1150,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,))
self.conn.commit()
- # There is no editor for author, so we do not need get_authors_with_ids or
- # delete_author_using_id. However, we can change the author's sort field, so
- # we provide that setter
+ def get_authors_with_ids(self):
+ result = self.conn.get('SELECT id,name,sort FROM authors')
+ if not result:
+ return []
+ return result
def set_sort_field_for_author(self, old_id, new_sort):
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
From f4854022a0db4d19f185462e9e665f46b4371e4b Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 13:43:05 +0100
Subject: [PATCH 10/13] Edit authors works.
---
...field_dialog.py => edit_authors_dialog.py} | 12 +--
.../gui2/dialogs/edit_authors_dialog.ui | 89 +++++++++++++++++++
src/calibre/gui2/dialogs/sort_field_dialog.ui | 87 ------------------
src/calibre/gui2/dialogs/tag_list_editor.ui | 3 +
src/calibre/gui2/tag_view.py | 33 +++----
src/calibre/library/database2.py | 8 +-
6 files changed, 123 insertions(+), 109 deletions(-)
rename src/calibre/gui2/dialogs/{sort_field_dialog.py => edit_authors_dialog.py} (88%)
create mode 100644 src/calibre/gui2/dialogs/edit_authors_dialog.ui
delete mode 100644 src/calibre/gui2/dialogs/sort_field_dialog.ui
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py
similarity index 88%
rename from src/calibre/gui2/dialogs/sort_field_dialog.py
rename to src/calibre/gui2/dialogs/edit_authors_dialog.py
index a73d0d8cf4..8d13f98f7a 100644
--- a/src/calibre/gui2/dialogs/sort_field_dialog.py
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py
@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
from PyQt4.Qt import Qt, QDialog, QTableWidgetItem, QAbstractItemView
from calibre.ebooks.metadata import author_to_author_sort
-from calibre.gui2.dialogs.sort_field_dialog_ui import Ui_SortFieldDialog
+from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog
class tableItem(QTableWidgetItem):
def __ge__(self, other):
@@ -15,11 +15,11 @@ class tableItem(QTableWidgetItem):
def __lt__(self, other):
return unicode(self.text()).lower() < unicode(other.text()).lower()
-class SortFieldDialog(QDialog, Ui_SortFieldDialog):
+class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
def __init__(self, parent, db, id_to_select):
QDialog.__init__(self, parent)
- Ui_SortFieldDialog.__init__(self)
+ Ui_EditAuthorsDialog.__init__(self)
self.setupUi(self)
self.buttonBox.accepted.connect(self.accepted)
@@ -42,22 +42,21 @@ class SortFieldDialog(QDialog, Ui_SortFieldDialog):
self.table.setItem(row, 0, aut)
self.table.setItem(row, 1, sort)
if id == id_to_select:
- select_item = aut
+ select_item = sort
if select_item is not None:
self.table.setCurrentItem(select_item)
+ self.table.editItem(select_item)
self.table.resizeColumnsToContents()
self.table.setSortingEnabled(True)
self.table.sortByColumn(1, Qt.AscendingOrder)
def accepted(self):
- print 'accepted!'
self.result = []
for row in range(0,self.table.rowCount()):
id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
aut = unicode(self.table.item(row, 0).text())
sort = unicode(self.table.item(row, 1).text())
- print id, aut, sort
orig_aut,orig_sort = self.authors[id]
if orig_aut != aut or orig_sort != sort:
self.result.append((id, orig_aut, aut, sort))
@@ -68,3 +67,4 @@ class SortFieldDialog(QDialog, Ui_SortFieldDialog):
c = self.table.item(row, 1)
if c is not None:
c.setText(author_to_author_sort(aut))
+ self.table.setCurrentItem(c)
diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.ui b/src/calibre/gui2/dialogs/edit_authors_dialog.ui
new file mode 100644
index 0000000000..4ac133700f
--- /dev/null
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.ui
@@ -0,0 +1,89 @@
+
+
+ EditAuthorsDialog
+
+
+
+ 0
+ 0
+ 410
+ 239
+
+
+
+
+ 0
+ 0
+
+
+
+ Manage authors
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+ true
+
+
+
+
+ buttonBox
+ table
+ buttonBox
+
+
+
+
+ buttonBox
+ accepted()
+ EditAuthorsDialog
+ accept()
+
+
+ 229
+ 211
+
+
+ 157
+ 234
+
+
+
+
+ buttonBox
+ rejected()
+ EditAuthorsDialog
+ reject()
+
+
+ 297
+ 217
+
+
+ 286
+ 234
+
+
+
+
+
diff --git a/src/calibre/gui2/dialogs/sort_field_dialog.ui b/src/calibre/gui2/dialogs/sort_field_dialog.ui
deleted file mode 100644
index bc6ffae4fc..0000000000
--- a/src/calibre/gui2/dialogs/sort_field_dialog.ui
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
- SortFieldDialog
-
-
-
- 0
- 0
- 518
- 262
-
-
-
-
- 0
- 0
-
-
-
- Edit sort field
-
-
-
-
- 10
- 10
- 501
- 231
-
-
-
- -
-
-
- 0
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
-
-
-
-
- buttonBox
- accepted()
- SortFieldDialog
- accept()
-
-
- 229
- 211
-
-
- 157
- 234
-
-
-
-
- buttonBox
- rejected()
- SortFieldDialog
- reject()
-
-
- 297
- 217
-
-
- 286
- 234
-
-
-
-
-
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui
index 4f57af745b..39076aa1f6 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.ui
+++ b/src/calibre/gui2/dialogs/tag_list_editor.ui
@@ -121,6 +121,9 @@
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+ true
+
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 27d73a0af8..83463128bd 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -23,7 +23,7 @@ from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
-from calibre.gui2.dialogs.sort_field_dialog import SortFieldDialog
+from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
class TagDelegate(QItemDelegate):
@@ -91,7 +91,7 @@ class TagsView(QTreeView): # {{{
user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
- author_sort_edit = pyqtSignal(object, object, object)
+ author_sort_edit = pyqtSignal(object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
@@ -176,7 +176,7 @@ class TagsView(QTreeView): # {{{
self.saved_search_edit.emit(category)
return
if action == 'edit_author_sort':
- self.author_sort_edit.emit(self, category, index)
+ self.author_sort_edit.emit(self, index)
return
if action == 'hide':
self.hidden_categories.add(category)
@@ -199,7 +199,6 @@ class TagsView(QTreeView): # {{{
tag_item = item
tag_name = item.tag.name
tag_id = item.tag.id
- tag_sort = item.tag.sort
item = item.parent
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
@@ -215,13 +214,13 @@ class TagsView(QTreeView): # {{{
(key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
self.db.field_metadata[key]['is_custom'] and \
self.db.field_metadata[key]['datatype'] != 'rating'):
- self.context_menu.addAction(_('Rename') + " '" + tag_name + "'",
+ self.context_menu.addAction(_('Rename \'%s\'')%tag_name,
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
if key == 'authors':
- self.context_menu.addAction(_('Edit sort for') + " '" + tag_name + "'",
- partial(self.context_menu_handler, action='edit_author_sort',
- category=tag_sort, index=tag_id))
+ self.context_menu.addAction(_('Edit sort for \'%s\'')%tag_name,
+ partial(self.context_menu_handler,
+ action='edit_author_sort', index=tag_id))
self.context_menu.addSeparator()
# Hide/Show/Restore categories
self.context_menu.addAction(_('Hide category %s') % category,
@@ -238,9 +237,12 @@ class TagsView(QTreeView): # {{{
self.context_menu.addSeparator()
if key in ['tags', 'publisher', 'series'] or \
self.db.field_metadata[key]['is_custom']:
- self.context_menu.addAction(_('Manage ') + category,
+ self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='open_editor',
category=tag_name, key=key))
+ elif key == 'authors':
+ self.context_menu.addAction(_('Manage %s')%category,
+ partial(self.context_menu_handler, action='edit_author_sort'))
elif key == 'search':
self.context_menu.addAction(_('Manage Saved Searches'),
partial(self.context_menu_handler, action='manage_searches',
@@ -725,16 +727,17 @@ class TagBrowserMixin(object): # {{{
self.saved_search.clear_to_help()
self.search.clear_to_help()
- def do_author_sort_edit(self, parent, text, id):
- editor = SortFieldDialog(parent, self.library_view.model().db, id)
+ def do_author_sort_edit(self, parent, id):
+ db = self.library_view.model().db
+ editor = EditAuthorsDialog(parent, db, id)
d = editor.exec_()
if d:
- print editor.result
for (id, old_author, new_author, new_sort) in editor.result:
if old_author != new_author:
- self.library_view.model().db.rename_author(id, new_author)
- self.library_view.model().db.set_sort_field_for_author \
- (id, unicode(new_sort))
+ # The id might change if the new author already exists
+ id = db.rename_author(id, new_author)
+ db.set_sort_field_for_author(id, unicode(new_sort))
+ self.library_view.model().refresh()
self.tags_view.recount()
# }}}
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 794474f821..e2302c1c77 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1160,6 +1160,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
(new_sort, old_id))
self.conn.commit()
+ # Now change all the author_sort fields in books by this author
+ bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,))
+ for (book_id,) in bks:
+ ss = self.author_sort_from_book(book_id, index_is_id=True)
+ self.set_author_sort(book_id, ss)
def rename_author(self, old_id, new_name):
# Make sure that any commas in new_name are changed to '|'!
@@ -1186,7 +1191,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.execute('UPDATE authors SET name=? WHERE id=?',
(new_name, old_id))
self.conn.commit()
- return
+ return new_id
# Author exists. To fix this, we must replace all the authors
# instead of replacing the one. Reason: db integrity checks can stop
# the rename process, which would leave everything half-done. We
@@ -1233,6 +1238,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.set_author_sort(book_id, ss)
# the caller will do a general refresh, so we don't need to
# do one here
+ return new_id
# end convenience methods
From 36f6e670221b05b0df7cb2c9c7d59f9739677c10 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 14:56:15 +0100
Subject: [PATCH 11/13] Small cleanups after testing
---
.../gui2/dialogs/edit_authors_dialog.py | 28 +++++++++++++------
src/calibre/library/database2.py | 4 +--
2 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py
index 8d13f98f7a..6e7eef3add 100644
--- a/src/calibre/gui2/dialogs/edit_authors_dialog.py
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py
@@ -23,7 +23,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.setupUi(self)
self.buttonBox.accepted.connect(self.accepted)
- self.table.cellChanged.connect(self.cell_changed)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setColumnCount(2)
@@ -43,13 +42,18 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setItem(row, 1, sort)
if id == id_to_select:
select_item = sort
+ self.table.resizeColumnsToContents()
+ # set up the signal after the table is filled
+ self.table.cellChanged.connect(self.cell_changed)
+
+ self.table.setSortingEnabled(True)
+ self.table.sortByColumn(1, Qt.AscendingOrder)
if select_item is not None:
self.table.setCurrentItem(select_item)
self.table.editItem(select_item)
- self.table.resizeColumnsToContents()
- self.table.setSortingEnabled(True)
- self.table.sortByColumn(1, Qt.AscendingOrder)
+ else:
+ self.table.setCurrentCell(0, 0)
def accepted(self):
self.result = []
@@ -63,8 +67,16 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
def cell_changed(self, row, col):
if col == 0:
- aut = unicode(self.table.item(row, 0).text())
+ item = self.table.item(row, 0)
+ aut = unicode(item.text())
c = self.table.item(row, 1)
- if c is not None:
- c.setText(author_to_author_sort(aut))
- self.table.setCurrentItem(c)
+ c.setText(author_to_author_sort(aut))
+ item = c
+ else:
+ item = self.table.item(row, 1)
+ self.table.setCurrentItem(item)
+ # disable and reenable sorting to force the sort now, so we can scroll
+ # to the item after it moves
+ self.table.setSortingEnabled(False)
+ self.table.setSortingEnabled(True)
+ self.table.scrollToItem(item)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index e2302c1c77..72f33b677c 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -938,8 +938,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def author_sort_from_authors(self, authors):
result = []
for aut in authors:
- aut = aut.replace(',', '|')
- r = self.conn.get('SELECT sort FROM authors WHERE name=?', (aut,), all=False)
+ r = self.conn.get('SELECT sort FROM authors WHERE name=?',
+ (aut.replace(',', '|'),), all=False)
if r is None:
result.append(author_to_author_sort(aut))
else:
From 4ed3c284636b7a3c9d4d2b84ba196f8584242226 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 16:36:30 +0100
Subject: [PATCH 12/13] Fix restrictions
---
src/calibre/library/database2.py | 2 +-
src/calibre/library/schema_upgrades.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 72f33b677c..a8ac6ce5bf 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -709,7 +709,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
query = '''SELECT id, {0}, count, avg_rating, sort
FROM tag_browser_{1}'''.format(cn, tn)
else:
- query = '''SELECT id, {0}, count, avg_rating
+ query = '''SELECT id, {0}, count, avg_rating, sort
FROM tag_browser_filtered_{1}'''.format(cn, tn)
if sort_on_count:
query += ' ORDER BY count DESC'
diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py
index 0d7df2f0cd..fcf27c4183 100644
--- a/src/calibre/library/schema_upgrades.py
+++ b/src/calibre/library/schema_upgrades.py
@@ -316,7 +316,7 @@ class SchemaUpgrade(object):
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
id,
- {vcn} as sort,
+ {vcn},
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
{cn}={tn}.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
From 34ff4216d5b4700c50125b8e902c27183f225ab0 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Mon, 14 Jun 2010 16:58:14 +0100
Subject: [PATCH 13/13] Ensure that strings are stripped before they are used
---
src/calibre/gui2/dialogs/edit_authors_dialog.py | 6 +++---
src/calibre/library/database2.py | 7 +++++--
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py
index 6e7eef3add..842fd7c943 100644
--- a/src/calibre/gui2/dialogs/edit_authors_dialog.py
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py
@@ -59,8 +59,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.result = []
for row in range(0,self.table.rowCount()):
id = self.table.item(row, 0).data(Qt.UserRole).toInt()[0]
- aut = unicode(self.table.item(row, 0).text())
- sort = unicode(self.table.item(row, 1).text())
+ aut = unicode(self.table.item(row, 0).text()).strip()
+ sort = unicode(self.table.item(row, 1).text()).strip()
orig_aut,orig_sort = self.authors[id]
if orig_aut != aut or orig_sort != sort:
self.result.append((id, orig_aut, aut, sort))
@@ -68,7 +68,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
def cell_changed(self, row, col):
if col == 0:
item = self.table.item(row, 0)
- aut = unicode(item.text())
+ aut = unicode(item.text()).strip()
c = self.table.item(row, 1)
c.setText(author_to_author_sort(aut))
item = c
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index a8ac6ce5bf..2fb22a27f4 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1045,6 +1045,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_tag(self, old_id, new_name):
+ new_name = new_name.strip()
new_id = self.conn.get(
'''SELECT id from tags
WHERE name=?''', (new_name,), all=False)
@@ -1084,6 +1085,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_series(self, old_id, new_name):
+ new_name = new_name.strip()
new_id = self.conn.get(
'''SELECT id from series
WHERE name=?''', (new_name,), all=False)
@@ -1128,6 +1130,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return result
def rename_publisher(self, old_id, new_name):
+ new_name = new_name.strip()
new_id = self.conn.get(
'''SELECT id from publishers
WHERE name=?''', (new_name,), all=False)
@@ -1158,7 +1161,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set_sort_field_for_author(self, old_id, new_sort):
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
- (new_sort, old_id))
+ (new_sort.strip(), old_id))
self.conn.commit()
# Now change all the author_sort fields in books by this author
bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,))
@@ -1168,7 +1171,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def rename_author(self, old_id, new_name):
# Make sure that any commas in new_name are changed to '|'!
- new_name = new_name.replace(',', '|')
+ new_name = new_name.replace(',', '|').strip()
# Get the list of books we must fix up, one way or the other
# Save the list so we can use it twice