mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit Book: Saved searched Dialog: Allow re-arranging the saved searches using drag and drop. Fixes #1669514 [Nested or Grouped Saved Searches in Edit Book](https://bugs.launchpad.net/calibre/+bug/1669514)
This commit is contained in:
parent
31eecbd8e8
commit
e0b8a11f1a
@ -1,36 +1,40 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
# License: GPLv3 Copyright: 2013, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
print_function)
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
import copy
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
import json
|
||||||
|
|
||||||
import json, copy
|
|
||||||
from functools import partial
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QWidget, QToolBar, Qt, QHBoxLayout, QSize, QIcon, QGridLayout, QLabel, QTimer,
|
QAbstractListModel, QApplication, QCheckBox, QComboBox, QFont, QFrame,
|
||||||
QPushButton, pyqtSignal, QComboBox, QCheckBox, QSizePolicy, QVBoxLayout, QFont,
|
QGridLayout, QHBoxLayout, QIcon, QItemSelection, QLabel, QLineEdit, QListView,
|
||||||
QLineEdit, QToolButton, QListView, QFrame, QApplication, QStyledItemDelegate,
|
QMenu, QMimeData, QModelIndex, QPushButton, QScrollArea, QSize, QSizePolicy,
|
||||||
QAbstractListModel, QModelIndex, QMenu, QItemSelection, QStackedLayout, QScrollArea)
|
QStackedLayout, QStyledItemDelegate, Qt, QTimer, QToolBar, QToolButton,
|
||||||
|
QVBoxLayout, QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
|
||||||
import regex
|
import regex
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml
|
from calibre import prepare_string_for_xml
|
||||||
from calibre.gui2 import error_dialog, info_dialog, choose_files, choose_save_file
|
from calibre.gui2 import choose_files, choose_save_file, error_dialog, info_dialog
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
from calibre.gui2.dialogs.message_box import MessageBox
|
from calibre.gui2.dialogs.message_box import MessageBox
|
||||||
from calibre.gui2.widgets2 import HistoryComboBox, FlowLayout
|
from calibre.gui2.tweak_book import current_container, editors, tprefs
|
||||||
from calibre.gui2.tweak_book import tprefs, editors, current_container
|
from calibre.gui2.tweak_book.editor.snippets import (
|
||||||
|
KEY, MODIFIER, SnippetTextEdit, find_matching_snip, parse_template,
|
||||||
|
string_length
|
||||||
|
)
|
||||||
from calibre.gui2.tweak_book.function_replace import (
|
from calibre.gui2.tweak_book.function_replace import (
|
||||||
FunctionBox, functions as replace_functions, FunctionEditor, remove_function, Function)
|
Function, FunctionBox, FunctionEditor, functions as replace_functions,
|
||||||
|
remove_function
|
||||||
|
)
|
||||||
from calibre.gui2.tweak_book.widgets import BusyCursor
|
from calibre.gui2.tweak_book.widgets import BusyCursor
|
||||||
from calibre.gui2.tweak_book.editor.snippets import find_matching_snip, parse_template, string_length, SnippetTextEdit, MODIFIER, KEY
|
from calibre.gui2.widgets2 import FlowLayout, HistoryComboBox
|
||||||
|
|
||||||
from calibre.utils.icu import primary_contains
|
from calibre.utils.icu import primary_contains
|
||||||
|
|
||||||
|
|
||||||
REGEX_FLAGS = regex.VERSION1 | regex.WORD | regex.FULLCASE | regex.MULTILINE | regex.UNICODE
|
REGEX_FLAGS = regex.VERSION1 | regex.WORD | regex.FULLCASE | regex.MULTILINE | regex.UNICODE
|
||||||
|
|
||||||
# The search panel {{{
|
# The search panel {{{
|
||||||
@ -538,6 +542,63 @@ class SearchesModel(QAbstractListModel):
|
|||||||
def rowCount(self, parent=QModelIndex()):
|
def rowCount(self, parent=QModelIndex()):
|
||||||
return len(self.filtered_searches)
|
return len(self.filtered_searches)
|
||||||
|
|
||||||
|
def supportedDropActions(self):
|
||||||
|
return Qt.MoveAction
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
ans = QAbstractListModel.flags(self, index)
|
||||||
|
if index.isValid():
|
||||||
|
ans |= Qt.ItemIsDragEnabled
|
||||||
|
else:
|
||||||
|
ans |= Qt.ItemIsDropEnabled
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def mimeTypes(self):
|
||||||
|
return ['x-calibre/searches-rows', 'application/vnd.text.list']
|
||||||
|
|
||||||
|
def mimeData(self, indices):
|
||||||
|
ans = QMimeData()
|
||||||
|
names, rows = [], []
|
||||||
|
for i in indices:
|
||||||
|
if i.isValid():
|
||||||
|
names.append(i.data())
|
||||||
|
rows.append(i.row())
|
||||||
|
ans.setData('x-calibre/searches-rows', ','.join(map(str, rows)).encode('ascii'))
|
||||||
|
ans.setData('application/vnd.text.list', '\n'.join(names).encode('utf-8'))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def dropMimeData(self, data, action, row, column, parent):
|
||||||
|
if parent.isValid() or action != Qt.MoveAction or not data.hasFormat('x-calibre/searches-rows') or not self.filtered_searches:
|
||||||
|
return False
|
||||||
|
rows = map(int, bytes(bytearray(data.data('x-calibre/searches-rows'))).decode('ascii').split(','))
|
||||||
|
rows.sort()
|
||||||
|
moved_searches = [self.searches[self.filtered_searches[r]] for r in rows]
|
||||||
|
moved_searches_q = {id(s) for s in moved_searches}
|
||||||
|
insert_at = max(0, min(row, len(self.filtered_searches)))
|
||||||
|
while insert_at < len(self.filtered_searches):
|
||||||
|
s = self.searches[self.filtered_searches[insert_at]]
|
||||||
|
if id(s) in moved_searches_q:
|
||||||
|
insert_at += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
insert_before = id(self.searches[self.filtered_searches[insert_at]]) if insert_at < len(self.filtered_searches) else None
|
||||||
|
visible_searches = {id(self.searches[self.filtered_searches[r]]) for r in self.filtered_searches}
|
||||||
|
unmoved_searches = list(filter(lambda s:id(s) not in moved_searches_q, self.searches))
|
||||||
|
if insert_before is None:
|
||||||
|
searches = unmoved_searches + moved_searches
|
||||||
|
else:
|
||||||
|
idx = {id(x):i for i, x in enumerate(unmoved_searches)}[insert_before]
|
||||||
|
searches = unmoved_searches[:idx] + moved_searches + unmoved_searches[idx:]
|
||||||
|
filtered_searches = []
|
||||||
|
for i, s in enumerate(searches):
|
||||||
|
if id(s) in visible_searches:
|
||||||
|
filtered_searches.append(i)
|
||||||
|
self.modelAboutToBeReset.emit()
|
||||||
|
self.searches, self.filtered_searches = searches, filtered_searches
|
||||||
|
self.modelReset.emit()
|
||||||
|
tprefs['saved_searches'] = self.searches
|
||||||
|
return True
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
try:
|
try:
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
@ -835,6 +896,8 @@ class SavedSearches(QWidget):
|
|||||||
self.delegate = SearchDelegate(searches)
|
self.delegate = SearchDelegate(searches)
|
||||||
searches.setItemDelegate(self.delegate)
|
searches.setItemDelegate(self.delegate)
|
||||||
searches.setAlternatingRowColors(True)
|
searches.setAlternatingRowColors(True)
|
||||||
|
searches.setDragEnabled(True), searches.setAcceptDrops(True), searches.setDragDropMode(searches.InternalMove)
|
||||||
|
searches.setDropIndicatorShown(True)
|
||||||
h.addLayout(stack, stretch=10)
|
h.addLayout(stack, stretch=10)
|
||||||
self.v = v = QVBoxLayout()
|
self.v = v = QVBoxLayout()
|
||||||
h.addLayout(v)
|
h.addLayout(v)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user