mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Tooltips in Tag Browser and allow sorting of TB by average rating as well
This commit is contained in:
commit
b2cccd1281
@ -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=[],
|
||||||
|
@ -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')):
|
||||||
|
@ -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()
|
||||||
|
@ -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 = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user