diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 306bbc77e6..1056f6ced6 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -43,8 +43,8 @@ def _config(): help=_('Notify when a new version is available')) c.add_opt('use_roman_numerals_for_series_number', default=True, help=_('Use Roman numerals for series number')) - c.add_opt('sort_by_popularity', default=False, - help=_('Sort tags list by popularity')) + c.add_opt('sort_tags_by', default='name', + help=_('Sort tags list by name, popularity, or rating')) c.add_opt('cover_flow_queue_length', default=6, help=_('Number of covers to show in the cover browsing mode')) c.add_opt('LRF_conversion_defaults', default=[], diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 5e5393fdc4..daea4e86ea 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -10,7 +10,7 @@ Browsing book collection by tags. from itertools import izip 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, \ QAbstractItemModel, QVariant, QModelIndex, QMenu, \ 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.edit_authors_dialog import EditAuthorsDialog -class TagDelegate(QItemDelegate): +class TagDelegate(QItemDelegate): # {{{ def paint(self, painter, option, index): item = index.internalPointer() @@ -54,6 +54,8 @@ class TagDelegate(QItemDelegate): model.data(index, Qt.DisplayRole).toString()) painter.restore() + # }}} + class TagsView(QTreeView): # {{{ refresh_required = pyqtSignal() @@ -77,12 +79,12 @@ class TagsView(QTreeView): # {{{ self.setHeaderHidden(True) 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._model = TagsModel(db, parent=self, hidden_categories=self.hidden_categories, search_restriction=None) - self.popularity = popularity + self.sort_by = sort_by self.tag_match = tag_match self.db = db self.search_restriction = None @@ -90,8 +92,9 @@ class TagsView(QTreeView): # {{{ self.setContextMenuPolicy(Qt.CustomContextMenu) self.clicked.connect(self.toggle) self.customContextMenuRequested.connect(self.show_context_menu) - self.popularity.setChecked(config['sort_by_popularity']) - self.popularity.stateChanged.connect(self.sort_changed) + pop = config['sort_tags_by'] + 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) db.add_listener(self.database_changed) @@ -102,8 +105,8 @@ class TagsView(QTreeView): # {{{ def match_all(self): return self.tag_match and self.tag_match.currentIndex() > 0 - def sort_changed(self, state): - config.set('sort_by_popularity', state == Qt.Checked) + def sort_changed(self, pop): + config.set('sort_tags_by', self.db.CATEGORY_SORTS[pop]) self.recount() def set_search_restriction(self, s): @@ -386,7 +389,7 @@ class TagsModel(QAbstractItemModel): # {{{ self.row_map = [] # 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() for i, r in enumerate(self.row_map): if self.hidden_categories and self.categories[i] in self.hidden_categories: @@ -430,11 +433,11 @@ class TagsModel(QAbstractItemModel): # {{{ # Now get the categories 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, ids=self.db.search('', return_matches=True)) 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 for category in tb_categories: @@ -448,7 +451,7 @@ class TagsModel(QAbstractItemModel): # {{{ return data 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: return False row_index = -1 @@ -657,7 +660,7 @@ class TagBrowserMixin(object): # {{{ def __init__(self, db): self.library_view.model().count_changed_signal.connect(self.tags_view.recount) 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.saved_search.clear_to_help) self.tags_view.tag_list_edit.connect(self.do_tags_list_edit) @@ -718,9 +721,13 @@ class TagBrowserWidget(QWidget): # {{{ parent.tags_view = TagsView(parent) self._layout.addWidget(parent.tags_view) - parent.popularity = QCheckBox(parent) - parent.popularity.setText(_('Sort by &popularity')) - self._layout.addWidget(parent.popularity) + parent.sort_by = QComboBox(parent) + # Must be in the same order as db2.CATEGORY_SORTS + 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) for x in (_('Match any'), _('Match all')): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 383c67a773..2226520cf2 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -430,7 +430,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin, # {{{ self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) 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.status_bar.clear_message() self.search.clear_to_help() diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 2fb22a27f4..321a5fce17 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -64,6 +64,10 @@ class Tag(object): self.state = state self.avg_rating = avg/2.0 if avg is not None else 0 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.icon = icon @@ -687,7 +691,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): tn=field['table'], col=field['link_column']), (id_,)) 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) categories = {} @@ -711,10 +717,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: 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' - else: + if sort == 'popularity': + query += ' ORDER BY count DESC, sort ASC' + elif sort == 'name': query += ' ORDER BY sort ASC' + else: + query += ' ORDER BY avg_rating DESC, sort ASC' data = self.conn.get(query) # 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: categories['formats'].append(Tag(fmt, count=count, icon=icon)) - if sort_on_count: - categories['formats'].sort(cmp=lambda x,y:cmp(x.count, y.count), - reverse=True) - else: - categories['formats'].sort(cmp=lambda x,y:cmp(x.name, y.name)) + if sort == 'popularity': + categories['formats'].sort(key=lambda x: x.count, reverse=True) + else: # no ratings exist to sort on + categories['formats'].sort(key = lambda x:x.name) #### Now do the user-defined 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 if icon_map is not None: icon_map[cat_name] = icon_map[':user'] - if sort_on_count: + if sort == 'popularity': 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: 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 #### items = []