Tooltips in Tag Browser and allow sorting of TB by average rating as well

This commit is contained in:
Kovid Goyal 2010-06-15 10:03:26 -06:00
commit b2cccd1281
4 changed files with 48 additions and 31 deletions

View File

@ -43,8 +43,8 @@ def _config():
help=_('Notify when a new version is available')) help=_('Notify when a new version is available'))
c.add_opt('use_roman_numerals_for_series_number', default=True, c.add_opt('use_roman_numerals_for_series_number', default=True,
help=_('Use Roman numerals for series number')) help=_('Use Roman numerals for series number'))
c.add_opt('sort_by_popularity', default=False, c.add_opt('sort_tags_by', default='name',
help=_('Sort tags list by popularity')) help=_('Sort tags list by name, popularity, or rating'))
c.add_opt('cover_flow_queue_length', default=6, c.add_opt('cover_flow_queue_length', default=6,
help=_('Number of covers to show in the cover browsing mode')) help=_('Number of covers to show in the cover browsing mode'))
c.add_opt('LRF_conversion_defaults', default=[], c.add_opt('LRF_conversion_defaults', default=[],

View File

@ -10,7 +10,7 @@ Browsing book collection by tags.
from itertools import izip from itertools import izip
from functools import partial from functools import partial
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QCheckBox, \ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \ QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, \
QAbstractItemModel, QVariant, QModelIndex, QMenu, \ QAbstractItemModel, QVariant, QModelIndex, QMenu, \
QPushButton, QWidget, QItemDelegate QPushButton, QWidget, QItemDelegate
@ -24,7 +24,7 @@ from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
class TagDelegate(QItemDelegate): class TagDelegate(QItemDelegate): # {{{
def paint(self, painter, option, index): def paint(self, painter, option, index):
item = index.internalPointer() item = index.internalPointer()
@ -54,6 +54,8 @@ class TagDelegate(QItemDelegate):
model.data(index, Qt.DisplayRole).toString()) model.data(index, Qt.DisplayRole).toString())
painter.restore() painter.restore()
# }}}
class TagsView(QTreeView): # {{{ class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal() refresh_required = pyqtSignal()
@ -77,12 +79,12 @@ class TagsView(QTreeView): # {{{
self.setHeaderHidden(True) self.setHeaderHidden(True)
self.setItemDelegate(TagDelegate(self)) self.setItemDelegate(TagDelegate(self))
def set_database(self, db, tag_match, popularity): def set_database(self, db, tag_match, sort_by):
self.hidden_categories = config['tag_browser_hidden_categories'] self.hidden_categories = config['tag_browser_hidden_categories']
self._model = TagsModel(db, parent=self, self._model = TagsModel(db, parent=self,
hidden_categories=self.hidden_categories, hidden_categories=self.hidden_categories,
search_restriction=None) search_restriction=None)
self.popularity = popularity self.sort_by = sort_by
self.tag_match = tag_match self.tag_match = tag_match
self.db = db self.db = db
self.search_restriction = None self.search_restriction = None
@ -90,8 +92,9 @@ class TagsView(QTreeView): # {{{
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
self.clicked.connect(self.toggle) self.clicked.connect(self.toggle)
self.customContextMenuRequested.connect(self.show_context_menu) self.customContextMenuRequested.connect(self.show_context_menu)
self.popularity.setChecked(config['sort_by_popularity']) pop = config['sort_tags_by']
self.popularity.stateChanged.connect(self.sort_changed) self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop))
self.sort_by.currentIndexChanged.connect(self.sort_changed)
self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) self.refresh_required.connect(self.recount, type=Qt.QueuedConnection)
db.add_listener(self.database_changed) db.add_listener(self.database_changed)
@ -102,8 +105,8 @@ class TagsView(QTreeView): # {{{
def match_all(self): def match_all(self):
return self.tag_match and self.tag_match.currentIndex() > 0 return self.tag_match and self.tag_match.currentIndex() > 0
def sort_changed(self, state): def sort_changed(self, pop):
config.set('sort_by_popularity', state == Qt.Checked) config.set('sort_tags_by', self.db.CATEGORY_SORTS[pop])
self.recount() self.recount()
def set_search_restriction(self, s): def set_search_restriction(self, s):
@ -386,7 +389,7 @@ class TagsModel(QAbstractItemModel): # {{{
self.row_map = [] self.row_map = []
# get_node_tree cannot return None here, because row_map is empty # get_node_tree cannot return None here, because row_map is empty
data = self.get_node_tree(config['sort_by_popularity']) data = self.get_node_tree(config['sort_tags_by'])
self.root_item = TagTreeItem() self.root_item = TagTreeItem()
for i, r in enumerate(self.row_map): for i, r in enumerate(self.row_map):
if self.hidden_categories and self.categories[i] in self.hidden_categories: if self.hidden_categories and self.categories[i] in self.hidden_categories:
@ -430,11 +433,11 @@ class TagsModel(QAbstractItemModel): # {{{
# Now get the categories # Now get the categories
if self.search_restriction: if self.search_restriction:
data = self.db.get_categories(sort_on_count=sort, data = self.db.get_categories(sort=sort,
icon_map=self.category_icon_map, icon_map=self.category_icon_map,
ids=self.db.search('', return_matches=True)) ids=self.db.search('', return_matches=True))
else: else:
data = self.db.get_categories(sort_on_count=sort, icon_map=self.category_icon_map) data = self.db.get_categories(sort=sort, icon_map=self.category_icon_map)
tb_categories = self.db.field_metadata tb_categories = self.db.field_metadata
for category in tb_categories: for category in tb_categories:
@ -448,7 +451,7 @@ class TagsModel(QAbstractItemModel): # {{{
return data return data
def refresh(self): def refresh(self):
data = self.get_node_tree(config['sort_by_popularity']) # get category data data = self.get_node_tree(config['sort_tags_by']) # get category data
if data is None: if data is None:
return False return False
row_index = -1 row_index = -1
@ -657,7 +660,7 @@ class TagBrowserMixin(object): # {{{
def __init__(self, db): def __init__(self, db):
self.library_view.model().count_changed_signal.connect(self.tags_view.recount) self.library_view.model().count_changed_signal.connect(self.tags_view.recount)
self.tags_view.set_database(self.library_view.model().db, self.tags_view.set_database(self.library_view.model().db,
self.tag_match, self.popularity) self.tag_match, self.sort_by)
self.tags_view.tags_marked.connect(self.search.search_from_tags) self.tags_view.tags_marked.connect(self.search.search_from_tags)
self.tags_view.tags_marked.connect(self.saved_search.clear_to_help) self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit) self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
@ -718,9 +721,13 @@ class TagBrowserWidget(QWidget): # {{{
parent.tags_view = TagsView(parent) parent.tags_view = TagsView(parent)
self._layout.addWidget(parent.tags_view) self._layout.addWidget(parent.tags_view)
parent.popularity = QCheckBox(parent) parent.sort_by = QComboBox(parent)
parent.popularity.setText(_('Sort by &popularity')) # Must be in the same order as db2.CATEGORY_SORTS
self._layout.addWidget(parent.popularity) for x in (_('Sort by name'), _('Sort by popularity'),
_('Sort by average rating')):
parent.sort_by.addItem(x)
parent.sort_by.setCurrentIndex(0)
self._layout.addWidget(parent.sort_by)
parent.tag_match = QComboBox(parent) parent.tag_match = QComboBox(parent)
for x in (_('Match any'), _('Match all')): for x in (_('Match any'), _('Match all')):

View File

@ -430,7 +430,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{
self.book_on_device(None, reset=True) self.book_on_device(None, reset=True)
db.set_book_on_device_func(self.book_on_device) db.set_book_on_device_func(self.book_on_device)
self.library_view.set_database(db) self.library_view.set_database(db)
self.tags_view.set_database(db, self.tag_match, self.popularity) self.tags_view.set_database(db, self.tag_match, self.sort_by)
self.library_view.model().set_book_on_device_func(self.book_on_device) self.library_view.model().set_book_on_device_func(self.book_on_device)
self.status_bar.clear_message() self.status_bar.clear_message()
self.search.clear_to_help() self.search.clear_to_help()

View File

@ -64,6 +64,10 @@ class Tag(object):
self.state = state self.state = state
self.avg_rating = avg/2.0 if avg is not None else 0 self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort self.sort = sort
if self.avg_rating > 0:
if tooltip:
tooltip = tooltip + ': '
tooltip = _('%sAverage rating is %3.1f')%(tooltip, self.avg_rating)
self.tooltip = tooltip self.tooltip = tooltip
self.icon = icon self.icon = icon
@ -687,7 +691,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
tn=field['table'], col=field['link_column']), (id_,)) tn=field['table'], col=field['link_column']), (id_,))
return set(x[0] for x in ans) return set(x[0] for x in ans)
def get_categories(self, sort_on_count=False, ids=None, icon_map=None): CATEGORY_SORTS = ('name', 'popularity', 'rating')
def get_categories(self, sort='name', ids=None, icon_map=None):
self.books_list_filter.change([] if not ids else ids) self.books_list_filter.change([] if not ids else ids)
categories = {} categories = {}
@ -711,10 +717,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
else: else:
query = '''SELECT id, {0}, count, avg_rating, sort query = '''SELECT id, {0}, count, avg_rating, sort
FROM tag_browser_filtered_{1}'''.format(cn, tn) FROM tag_browser_filtered_{1}'''.format(cn, tn)
if sort_on_count: if sort == 'popularity':
query += ' ORDER BY count DESC' query += ' ORDER BY count DESC, sort ASC'
else: elif sort == 'name':
query += ' ORDER BY sort ASC' query += ' ORDER BY sort ASC'
else:
query += ' ORDER BY avg_rating DESC, sort ASC'
data = self.conn.get(query) data = self.conn.get(query)
# icon_map is not None if get_categories is to store an icon and # icon_map is not None if get_categories is to store an icon and
@ -770,11 +778,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if count > 0: if count > 0:
categories['formats'].append(Tag(fmt, count=count, icon=icon)) categories['formats'].append(Tag(fmt, count=count, icon=icon))
if sort_on_count: if sort == 'popularity':
categories['formats'].sort(cmp=lambda x,y:cmp(x.count, y.count), categories['formats'].sort(key=lambda x: x.count, reverse=True)
reverse=True) else: # no ratings exist to sort on
else: categories['formats'].sort(key = lambda x:x.name)
categories['formats'].sort(cmp=lambda x,y:cmp(x.name, y.name))
#### Now do the user-defined categories. #### #### Now do the user-defined categories. ####
user_categories = prefs['user_categories'] user_categories = prefs['user_categories']
@ -799,12 +806,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Not a problem if we accumulate entries in the icon map # Not a problem if we accumulate entries in the icon map
if icon_map is not None: if icon_map is not None:
icon_map[cat_name] = icon_map[':user'] icon_map[cat_name] = icon_map[':user']
if sort_on_count: if sort == 'popularity':
categories[cat_name] = \ categories[cat_name] = \
sorted(items, cmp=(lambda x, y: cmp(y.count, x.count))) sorted(items, key=lambda x: x.count, reverse=True)
elif sort == 'name':
categories[cat_name] = \
sorted(items, key=lambda x: x.sort.lower())
else: else:
categories[cat_name] = \ categories[cat_name] = \
sorted(items, cmp=(lambda x, y: cmp(x.name.lower(), y.name.lower()))) sorted(items, key=lambda x:x.avg_rating, reverse=True)
#### Finally, the saved searches category #### #### Finally, the saved searches category ####
items = [] items = []