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