mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow creating sorts based on multiple columns (Add the Sort action to the toolbar via Preferences->Toolbars & menus)
Fixes #1945891 [[Enhancement] Select sortable columns in a separate window](https://bugs.launchpad.net/calibre/+bug/1945891)
This commit is contained in:
parent
9249e92c0a
commit
ecdc1007c1
@ -7,10 +7,10 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from qt.core import QAction, QIcon, QToolButton, pyqtSignal
|
from qt.core import QAction, QDialog, QIcon, QToolButton, pyqtSignal
|
||||||
|
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import primary_sort_key
|
||||||
from polyglot.builtins import iteritems
|
from polyglot.builtins import iteritems
|
||||||
|
|
||||||
SORT_HIDDEN_PREF = 'sort-action-hidden-fields'
|
SORT_HIDDEN_PREF = 'sort-action-hidden-fields'
|
||||||
@ -98,8 +98,9 @@ class SortByAction(InterfaceAction):
|
|||||||
name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
|
name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
|
||||||
hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
|
hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
|
||||||
hidden_items_menu = menu.addMenu(_('Select sortable columns'))
|
hidden_items_menu = menu.addMenu(_('Select sortable columns'))
|
||||||
|
menu.addAction(_('Sort on multiple columns'), self.choose_multisort)
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
all_names = sorted(name_map, key=sort_key)
|
all_names = sorted(name_map, key=primary_sort_key)
|
||||||
for name in all_names:
|
for name in all_names:
|
||||||
key = name_map[name]
|
key = name_map[name]
|
||||||
ac = hidden_items_menu.addAction(name)
|
ac = hidden_items_menu.addAction(name)
|
||||||
@ -124,6 +125,12 @@ class SortByAction(InterfaceAction):
|
|||||||
sac.sort_requested.connect(self.sort_requested)
|
sac.sort_requested.connect(self.sort_requested)
|
||||||
menu.addAction(sac)
|
menu.addAction(sac)
|
||||||
|
|
||||||
|
def choose_multisort(self):
|
||||||
|
from calibre.gui2.dialogs.multisort import ChooseMultiSort
|
||||||
|
d = ChooseMultiSort(self.gui.current_db, parent=self.gui, is_device_connected=self.gui.device_connected)
|
||||||
|
if d.exec_() == QDialog.DialogCode.Accepted:
|
||||||
|
self.gui.library_view.multisort(d.current_sort_spec)
|
||||||
|
|
||||||
def sort_requested(self, key, ascending):
|
def sort_requested(self, key, ascending):
|
||||||
if ascending is None:
|
if ascending is None:
|
||||||
self.gui.library_view.intelligent_sort(key, True)
|
self.gui.library_view.intelligent_sort(key, True)
|
||||||
|
123
src/calibre/gui2/dialogs/multisort.py
Normal file
123
src/calibre/gui2/dialogs/multisort.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
from qt.core import (
|
||||||
|
QAbstractItemView, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QSize, Qt,
|
||||||
|
QVBoxLayout
|
||||||
|
)
|
||||||
|
|
||||||
|
from calibre import prepare_string_for_xml
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
|
from calibre.gui2.actions.sort import SORT_HIDDEN_PREF
|
||||||
|
from calibre.gui2.widgets2 import Dialog
|
||||||
|
from calibre.utils.icu import primary_sort_key
|
||||||
|
|
||||||
|
ascending_symbol = '⏷'
|
||||||
|
descending_symbol = '⏶'
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseMultiSort(Dialog):
|
||||||
|
|
||||||
|
def __init__(self, db, is_device_connected=False, parent=None, hidden_pref=SORT_HIDDEN_PREF):
|
||||||
|
self.db = db.new_api
|
||||||
|
self.hidden_fields = set(self.db.pref(SORT_HIDDEN_PREF, default=()) or ())
|
||||||
|
if not is_device_connected:
|
||||||
|
self.hidden_fields.add('ondevice')
|
||||||
|
fm = self.db.field_metadata
|
||||||
|
self.key_map = fm.ui_sortable_field_keys().copy()
|
||||||
|
self.name_map = {v:k for k, v in self.key_map.items()}
|
||||||
|
self.all_names = sorted(self.name_map, key=primary_sort_key)
|
||||||
|
self.sort_order_map = dict.fromkeys(self.key_map, True)
|
||||||
|
super().__init__(_('Sort by multiple columns'), 'multisort-chooser', parent=parent)
|
||||||
|
|
||||||
|
def sizeHint(self):
|
||||||
|
return QSize(600, 400)
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.vl = vl = QVBoxLayout(self)
|
||||||
|
self.hl = hl = QHBoxLayout()
|
||||||
|
self.la = la = QLabel(_(
|
||||||
|
'Pick multiple columns to sort by. Drag and drop to re-arrange. Higher columns are more important.'
|
||||||
|
' Ascending or descending order can be toggled by clicking the column name at the bottom'
|
||||||
|
' of this dialog, after having selected it.'))
|
||||||
|
la.setWordWrap(True)
|
||||||
|
vl.addWidget(la)
|
||||||
|
vl.addLayout(hl)
|
||||||
|
self.order_label = la = QLabel('')
|
||||||
|
la.setTextFormat(Qt.TextFormat.RichText)
|
||||||
|
la.setWordWrap(True)
|
||||||
|
la.linkActivated.connect(self.link_activated)
|
||||||
|
vl.addWidget(la)
|
||||||
|
vl.addWidget(self.bb)
|
||||||
|
|
||||||
|
self.column_list = cl = QListWidget(self)
|
||||||
|
hl.addWidget(cl)
|
||||||
|
for name in self.all_names:
|
||||||
|
i = QListWidgetItem(cl)
|
||||||
|
i.setText(name)
|
||||||
|
i.setData(Qt.ItemDataRole.UserRole, self.name_map[name])
|
||||||
|
cl.addItem(i)
|
||||||
|
i.setCheckState(Qt.CheckState.Unchecked)
|
||||||
|
if self.name_map[name] in self.hidden_fields:
|
||||||
|
i.setHidden(True)
|
||||||
|
cl.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||||
|
cl.currentRowChanged.connect(self.current_changed)
|
||||||
|
cl.itemDoubleClicked.connect(self.item_double_clicked)
|
||||||
|
cl.setCurrentRow(0)
|
||||||
|
cl.itemChanged.connect(self.update_order_label)
|
||||||
|
cl.model().rowsMoved.connect(self.update_order_label)
|
||||||
|
|
||||||
|
def item_double_clicked(self, item):
|
||||||
|
cs = item.checkState()
|
||||||
|
item.setCheckState(Qt.CheckState.Checked if cs == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked)
|
||||||
|
|
||||||
|
def current_changed(self):
|
||||||
|
self.update_order_label()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_sort_spec(self):
|
||||||
|
ans = []
|
||||||
|
cl = self.column_list
|
||||||
|
for item in (cl.item(r) for r in range(cl.count())):
|
||||||
|
if item.checkState() == Qt.CheckState.Checked:
|
||||||
|
k = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
ans.append((k, self.sort_order_map[k]))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def update_order_label(self):
|
||||||
|
t = ''
|
||||||
|
for i, (k, ascending) in enumerate(self.current_sort_spec):
|
||||||
|
name = self.key_map[k]
|
||||||
|
symbol = ascending_symbol if ascending else descending_symbol
|
||||||
|
if i != 0:
|
||||||
|
t += ' :: '
|
||||||
|
q = bytes.hex(k.encode('utf-8'))
|
||||||
|
dname = prepare_string_for_xml(name).replace(" ", " ")
|
||||||
|
t += f' <a href="{q}" style="text-decoration: none">{dname} {symbol}</a>'
|
||||||
|
if t:
|
||||||
|
t = _('Effective sort') + ': ' + t
|
||||||
|
self.order_label.setText(t)
|
||||||
|
|
||||||
|
def link_activated(self, url):
|
||||||
|
key = bytes.fromhex(url).decode('utf-8')
|
||||||
|
self.sort_order_map[key] ^= True
|
||||||
|
self.update_order_label()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if not self.current_sort_spec:
|
||||||
|
return error_dialog(self, _('No sort selected'), _(
|
||||||
|
'You must select at least one column on which to sort'), show=True)
|
||||||
|
super().accept()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from calibre.gui2 import Application
|
||||||
|
app = Application([])
|
||||||
|
from calibre.library import db
|
||||||
|
d = ChooseMultiSort(db())
|
||||||
|
d.exec_()
|
||||||
|
print(d.current_sort_spec)
|
||||||
|
del d
|
||||||
|
del app
|
Loading…
x
Reference in New Issue
Block a user