mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Saved searches: Add import/export functionality
This commit is contained in:
parent
3813b7ac12
commit
f52aa7a615
@ -6,19 +6,20 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
import json, copy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QWidget, QToolBar, Qt, QHBoxLayout, QSize, QIcon, QGridLayout, QLabel,
|
QWidget, QToolBar, Qt, QHBoxLayout, QSize, QIcon, QGridLayout, QLabel, QTimer,
|
||||||
QPushButton, pyqtSignal, QComboBox, QCheckBox, QSizePolicy, QVBoxLayout,
|
QPushButton, pyqtSignal, QComboBox, QCheckBox, QSizePolicy, QVBoxLayout,
|
||||||
QLineEdit, QToolButton, QListView, QFrame, QApplication, QStyledItemDelegate,
|
QLineEdit, QToolButton, QListView, QFrame, QApplication, QStyledItemDelegate,
|
||||||
QAbstractListModel, QVariant, QFormLayout, QModelIndex)
|
QAbstractListModel, QVariant, QFormLayout, QModelIndex, QMenu, QItemSelection)
|
||||||
|
|
||||||
import regex
|
import regex
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml
|
from calibre import prepare_string_for_xml
|
||||||
from calibre.gui2 import NONE, error_dialog, info_dialog
|
from calibre.gui2 import NONE, error_dialog, info_dialog, choose_files, choose_save_file
|
||||||
from calibre.gui2.dialogs.message_box import MessageBox
|
from calibre.gui2.dialogs.message_box import MessageBox
|
||||||
from calibre.gui2.widgets2 import HistoryLineEdit2
|
from calibre.gui2.widgets2 import HistoryLineEdit2
|
||||||
from calibre.gui2.tweak_book import tprefs, editors, current_container
|
from calibre.gui2.tweak_book import tprefs, editors, current_container
|
||||||
@ -391,9 +392,9 @@ class SearchesModel(QAbstractListModel):
|
|||||||
self.dataChanged.emit(self.index(b), self.index(b))
|
self.dataChanged.emit(self.index(b), self.index(b))
|
||||||
tprefs['saved_searches'] = self.searches
|
tprefs['saved_searches'] = self.searches
|
||||||
|
|
||||||
def add_search(self):
|
def add_searches(self, count=1):
|
||||||
self.searches = tprefs['saved_searches']
|
self.searches = tprefs['saved_searches']
|
||||||
self.filtered_searches.append(len(self.searches) - 1)
|
self.filtered_searches.extend(xrange(len(self.searches) - 1, len(self.searches) - 1 - count, -1))
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def remove_searches(self, rows):
|
def remove_searches(self, rows):
|
||||||
@ -601,6 +602,13 @@ class SavedSearches(Dialog):
|
|||||||
l.addWidget(self.bb)
|
l.addWidget(self.bb)
|
||||||
self.bb.clear()
|
self.bb.clear()
|
||||||
self.bb.addButton(self.bb.Close)
|
self.bb.addButton(self.bb.Close)
|
||||||
|
self.ib = b = self.bb.addButton(_('&Import'), self.bb.ActionRole)
|
||||||
|
b.clicked.connect(self.import_searches)
|
||||||
|
self.eb = b = self.bb.addButton(_('E&xport'), self.bb.ActionRole)
|
||||||
|
self.em = m = QMenu(_('Export'))
|
||||||
|
m.addAction(_('Export All'), lambda : QTimer.singleShot(0, partial(self.export_searches, all=True)))
|
||||||
|
m.addAction(_('Export Selected'), lambda : QTimer.singleShot(0, partial(self.export_searches, all=False)))
|
||||||
|
b.setMenu(m)
|
||||||
|
|
||||||
self.searches.setFocus(Qt.OtherFocusReason)
|
self.searches.setFocus(Qt.OtherFocusReason)
|
||||||
|
|
||||||
@ -639,12 +647,13 @@ class SavedSearches(Dialog):
|
|||||||
continue
|
continue
|
||||||
seen.add(index.row())
|
seen.add(index.row())
|
||||||
search = SearchWidget.DEFAULT_STATE.copy()
|
search = SearchWidget.DEFAULT_STATE.copy()
|
||||||
|
del search['mode']
|
||||||
search_index, s = index.data(Qt.UserRole).toPyObject()
|
search_index, s = index.data(Qt.UserRole).toPyObject()
|
||||||
search.update(s)
|
search.update(s)
|
||||||
search['wrap'] = self.wrap
|
search['wrap'] = self.wrap
|
||||||
search['direction'] = self.direction
|
search['direction'] = self.direction
|
||||||
search['where'] = self.where
|
search['where'] = self.where
|
||||||
search['mode'] = 'regex'
|
search['mode'] = search.get('mode', 'regex')
|
||||||
searches.append(search)
|
searches.append(search)
|
||||||
if not searches:
|
if not searches:
|
||||||
return
|
return
|
||||||
@ -681,7 +690,7 @@ class SavedSearches(Dialog):
|
|||||||
|
|
||||||
def _add_search(self, d):
|
def _add_search(self, d):
|
||||||
if d.exec_() == d.Accepted:
|
if d.exec_() == d.Accepted:
|
||||||
self.model.add_search()
|
self.model.add_searches()
|
||||||
index = self.model.index(self.model.rowCount() - 1)
|
index = self.model.index(self.model.rowCount() - 1)
|
||||||
self.searches.scrollTo(index)
|
self.searches.scrollTo(index)
|
||||||
sm = self.searches.selectionModel()
|
sm = self.searches.selectionModel()
|
||||||
@ -702,6 +711,57 @@ class SavedSearches(Dialog):
|
|||||||
self.description.setText(_('{2} (Case sensitive: {3} Dot All: {4})\nFind: {0}\nReplace: {1}').format(
|
self.description.setText(_('{2} (Case sensitive: {3} Dot All: {4})\nFind: {0}\nReplace: {1}').format(
|
||||||
search.get('find', ''), search.get('replace', ''), search.get('name', ''), cs, da))
|
search.get('find', ''), search.get('replace', ''), search.get('name', ''), cs, da))
|
||||||
|
|
||||||
|
def import_searches(self):
|
||||||
|
path = choose_files(self, 'import_saved_searches', _('Choose file'), filters=[
|
||||||
|
(_('Saved Searches'), ['json'])], all_files=False, select_only_single_file=True)
|
||||||
|
if path:
|
||||||
|
with open(path[0], 'rb') as f:
|
||||||
|
obj = json.loads(f.read())
|
||||||
|
needed_keys = {'name', 'find', 'replace', 'case_sensitive', 'dot_all', 'mode'}
|
||||||
|
def err():
|
||||||
|
error_dialog(self, _('Invalid data'), _(
|
||||||
|
'The file %s does not contain valid saved searches') % path, show=True)
|
||||||
|
if not isinstance(obj, dict) or not 'version' in obj or not 'searches' in obj:
|
||||||
|
return err()
|
||||||
|
searches = []
|
||||||
|
for item in obj['searches']:
|
||||||
|
if not isinstance(item, dict) or not set(item.iterkeys()).issuperset(needed_keys):
|
||||||
|
return err
|
||||||
|
searches.append({k:item[k] for k in needed_keys})
|
||||||
|
|
||||||
|
if searches:
|
||||||
|
tprefs['saved_searches'] = tprefs['saved_searches'] + searches
|
||||||
|
count = len(searches)
|
||||||
|
self.model.add_searches(count=count)
|
||||||
|
sm = self.searches.selectionModel()
|
||||||
|
top, bottom = self.model.index(self.model.rowCount() - count), self.model.index(self.model.rowCount() - 1)
|
||||||
|
sm.select(QItemSelection(top, bottom), sm.ClearAndSelect)
|
||||||
|
self.searches.scrollTo(bottom)
|
||||||
|
|
||||||
|
def export_searches(self, all=True):
|
||||||
|
if all:
|
||||||
|
searches = copy.deepcopy(tprefs['saved_searches'])
|
||||||
|
if not searches:
|
||||||
|
return error_dialog(self, _('No searches'), _(
|
||||||
|
'No searches available to be saved'), show=True)
|
||||||
|
else:
|
||||||
|
searches = []
|
||||||
|
for index in self.searches.selectionModel().selectedIndexes():
|
||||||
|
search = index.data(Qt.UserRole).toPyObject()[-1]
|
||||||
|
searches.append(search.copy())
|
||||||
|
if not searches:
|
||||||
|
return error_dialog(self, _('No searches'), _(
|
||||||
|
'No searches selected'), show=True)
|
||||||
|
[s.__setitem__('mode', s.get('mode', 'regex')) for s in searches]
|
||||||
|
path = choose_save_file(self, 'export-saved-searches', _('Choose file'), filters=[
|
||||||
|
(_('Saved Searches'), ['json'])], all_files=False)
|
||||||
|
if path:
|
||||||
|
if not path.lower().endswith('.json'):
|
||||||
|
path += '.json'
|
||||||
|
raw = json.dumps({'version':1, 'searches':searches}, ensure_ascii=False, indent=2, sort_keys=True)
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(raw.encode('utf-8'))
|
||||||
|
|
||||||
def validate_search_request(name, searchable_names, has_marked_text, state, gui_parent):
|
def validate_search_request(name, searchable_names, has_marked_text, state, gui_parent):
|
||||||
err = None
|
err = None
|
||||||
where = state['where']
|
where = state['where']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user