mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement find over multiple files and in marked text regions
This commit is contained in:
parent
5291283265
commit
91a42ff6cf
@ -7,10 +7,11 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import tempfile, shutil, sys, os
|
import tempfile, shutil, sys, os
|
||||||
|
from collections import OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
|
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt, QCursor,
|
||||||
QDialogButtonBox, QIcon, QTimer, QPixmap, QTextBrowser, QVBoxLayout)
|
QDialogButtonBox, QIcon, QTimer, QPixmap, QTextBrowser, QVBoxLayout)
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
@ -272,7 +273,7 @@ class Boss(QObject):
|
|||||||
# Ensure the search panel is visible
|
# Ensure the search panel is visible
|
||||||
sp.setVisible(True)
|
sp.setVisible(True)
|
||||||
ed = self.gui.central.current_editor
|
ed = self.gui.central.current_editor
|
||||||
name = None
|
name = editor = None
|
||||||
for n, x in editors.iteritems():
|
for n, x in editors.iteritems():
|
||||||
if x is ed:
|
if x is ed:
|
||||||
name = n
|
name = n
|
||||||
@ -295,47 +296,74 @@ class Boss(QObject):
|
|||||||
if err:
|
if err:
|
||||||
return error_dialog(self.gui, _('Cannot search'), err, show=True)
|
return error_dialog(self.gui, _('Cannot search'), err, show=True)
|
||||||
del err
|
del err
|
||||||
|
|
||||||
|
files = OrderedDict()
|
||||||
|
do_all = state['wrap'] or action in {'replace-all', 'count'}
|
||||||
|
marked = False
|
||||||
if where == 'current':
|
if where == 'current':
|
||||||
files = [name]
|
|
||||||
editor = ed
|
editor = ed
|
||||||
elif where in {'styles', 'text', 'selected'}:
|
elif where in {'styles', 'text', 'selected'}:
|
||||||
files = searchable_names[where]
|
files = searchable_names[where]
|
||||||
if name in files:
|
if name in files:
|
||||||
|
# Start searching in the current editor
|
||||||
editor = ed
|
editor = ed
|
||||||
else:
|
# Re-order the list of other files so that we search in the same
|
||||||
common = set(editors).intersection(set(files))
|
# order every time. Depending on direction, search the files
|
||||||
if common:
|
# that come after the current file, or before the current file,
|
||||||
name = next(x for x in files if x in common)
|
# first.
|
||||||
editor = editors[name]
|
lfiles = list(files)
|
||||||
self.gui.central.show_editor(editor)
|
idx = lfiles.index(name)
|
||||||
|
before, after = lfiles[:idx], lfiles[idx+1:]
|
||||||
|
if state['direction'] == 'up':
|
||||||
|
lfiles = list(reversed(before))
|
||||||
|
if do_all:
|
||||||
|
lfiles += list(reversed(after)) + [name]
|
||||||
else:
|
else:
|
||||||
pass # TODO: Find the first name with a match and open its editor
|
lfiles = after
|
||||||
|
if do_all:
|
||||||
|
lfiles += before + [name]
|
||||||
|
files = OrderedDict((m, files[m]) for m in lfiles)
|
||||||
else:
|
else:
|
||||||
files = [name]
|
editor = ed
|
||||||
pass # marked text TODO: Implement this
|
marked = True
|
||||||
|
|
||||||
def no_match():
|
def no_match():
|
||||||
|
msg = '<p>' + _('No matches were found for %s.') % state['find']
|
||||||
|
if not state['wrap']:
|
||||||
|
msg += '<p>' + _('You have turned off search wrapping, so all text might not have been searched.'
|
||||||
|
' Try the search again, with wrapping enabled. Wrapping is enabled via the'
|
||||||
|
' "Wrap" checkbox at the bottom of the search panel.')
|
||||||
return error_dialog(
|
return error_dialog(
|
||||||
self.gui, _('Not found'), _(
|
self.gui, _('Not found'), msg, show=True)
|
||||||
'No matches were found for %s') % state['find'], show=True)
|
|
||||||
|
|
||||||
pat = sp.get_regex(state)
|
pat = sp.get_regex(state)
|
||||||
|
|
||||||
def do_find():
|
def do_find():
|
||||||
found = editor.find(pat)
|
if editor is not None:
|
||||||
if found:
|
if editor.find(pat, marked=marked):
|
||||||
return
|
return
|
||||||
if len(files) == 1:
|
if not files:
|
||||||
if not state['wrap']:
|
if not state['wrap']:
|
||||||
return no_match()
|
return no_match()
|
||||||
found = editor.find(pat, wrap=True)
|
return editor.find(pat, wrap=True, marked=marked) or no_match()
|
||||||
if not found:
|
for fname, syntax in files.iteritems():
|
||||||
return no_match()
|
if fname in editors:
|
||||||
else:
|
if not editors[fname].find(pat, complete=True):
|
||||||
pass # TODO: handle multiple file search
|
continue
|
||||||
|
return self.show_editor(fname)
|
||||||
|
raw = current_container().raw_data(fname)
|
||||||
|
if pat.search(raw) is not None:
|
||||||
|
self.edit_file(fname, syntax)
|
||||||
|
if editors[fname].find(pat, complete=True):
|
||||||
|
return
|
||||||
|
return no_match()
|
||||||
|
|
||||||
if action == 'find':
|
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
return do_find()
|
try:
|
||||||
|
if action == 'find':
|
||||||
|
return do_find()
|
||||||
|
finally:
|
||||||
|
QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
def save_book(self):
|
def save_book(self):
|
||||||
c = current_container()
|
c = current_container()
|
||||||
|
@ -154,12 +154,59 @@ class TextEdit(QPlainTextEdit):
|
|||||||
self.current_search_mark = None
|
self.current_search_mark = None
|
||||||
self.update_extra_selections()
|
self.update_extra_selections()
|
||||||
|
|
||||||
def find(self, pat, wrap=False):
|
def find_in_marked(self, pat, wrap=False):
|
||||||
|
if self.current_search_mark is None:
|
||||||
|
return False
|
||||||
|
csm = self.current_search_mark.cursor
|
||||||
reverse = pat.flags & regex.REVERSE
|
reverse = pat.flags & regex.REVERSE
|
||||||
c = self.textCursor()
|
c = self.textCursor()
|
||||||
c.clearSelection()
|
c.clearSelection()
|
||||||
pos = c.Start if reverse else c.End
|
m_start = min(csm.position(), csm.anchor())
|
||||||
|
m_end = max(csm.position(), csm.anchor())
|
||||||
|
if c.position() < m_start:
|
||||||
|
c.setPosition(m_start)
|
||||||
|
if c.position() > m_end:
|
||||||
|
c.setPosition(m_end)
|
||||||
|
pos = m_start if reverse else m_end
|
||||||
if wrap:
|
if wrap:
|
||||||
|
pos = m_end if reverse else m_start
|
||||||
|
c.setPosition(pos, c.KeepAnchor)
|
||||||
|
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
|
||||||
|
m = pat.search(raw)
|
||||||
|
if m is None:
|
||||||
|
return False
|
||||||
|
start, end = m.span()
|
||||||
|
if start == end:
|
||||||
|
return False
|
||||||
|
if wrap:
|
||||||
|
if reverse:
|
||||||
|
textpos = c.anchor()
|
||||||
|
start, end = textpos + end, textpos + start
|
||||||
|
else:
|
||||||
|
start, end = m_start + start, m_start + end
|
||||||
|
else:
|
||||||
|
if reverse:
|
||||||
|
start, end = m_start + end, m_start + start
|
||||||
|
else:
|
||||||
|
start, end = c.anchor() + start, c.anchor() + end
|
||||||
|
|
||||||
|
c.clearSelection()
|
||||||
|
c.setPosition(start)
|
||||||
|
c.setPosition(end, c.KeepAnchor)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def find(self, pat, wrap=False, marked=False, complete=False):
|
||||||
|
if marked:
|
||||||
|
return self.find_in_marked(pat, wrap=wrap)
|
||||||
|
reverse = pat.flags & regex.REVERSE
|
||||||
|
c = self.textCursor()
|
||||||
|
c.clearSelection()
|
||||||
|
if complete:
|
||||||
|
# Search the entire text
|
||||||
|
c.movePosition(c.End if reverse else c.Start)
|
||||||
|
pos = c.Start if reverse else c.End
|
||||||
|
if wrap and not complete:
|
||||||
pos = c.End if reverse else c.Start
|
pos = c.End if reverse else c.Start
|
||||||
c.movePosition(pos, c.KeepAnchor)
|
c.movePosition(pos, c.KeepAnchor)
|
||||||
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
|
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
|
||||||
@ -169,7 +216,7 @@ class TextEdit(QPlainTextEdit):
|
|||||||
start, end = m.span()
|
start, end = m.span()
|
||||||
if start == end:
|
if start == end:
|
||||||
return False
|
return False
|
||||||
if wrap:
|
if wrap and not complete:
|
||||||
if reverse:
|
if reverse:
|
||||||
textpos = c.anchor()
|
textpos = c.anchor()
|
||||||
start, end = textpos + end, textpos + start
|
start, end = textpos + end, textpos + start
|
||||||
|
@ -7,6 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
from collections import OrderedDict
|
||||||
from PyQt4.Qt import (
|
from PyQt4.Qt import (
|
||||||
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
|
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
|
||||||
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal)
|
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal)
|
||||||
@ -17,6 +18,7 @@ from calibre.ebooks.oeb.polish.container import guess_type
|
|||||||
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
|
from calibre.ebooks.oeb.polish.cover import get_cover_page_name, get_raster_cover_name
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.gui2.tweak_book import current_container
|
from calibre.gui2.tweak_book import current_container
|
||||||
|
from calibre.gui2.tweak_book.editor import syntax_from_mime
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
TOP_ICON_SIZE = 24
|
TOP_ICON_SIZE = 24
|
||||||
@ -344,18 +346,18 @@ class FileList(QTreeWidget):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def searchable_names(self):
|
def searchable_names(self):
|
||||||
ans = {'text':[], 'styles':[], 'selected':[]}
|
ans = {'text':OrderedDict(), 'styles':OrderedDict(), 'selected':OrderedDict()}
|
||||||
for item in self.all_files:
|
for item in self.all_files:
|
||||||
category = unicode(item.data(0, CATEGORY_ROLE).toString())
|
category = unicode(item.data(0, CATEGORY_ROLE).toString())
|
||||||
mime = unicode(item.data(0, MIME_ROLE).toString())
|
mime = unicode(item.data(0, MIME_ROLE).toString())
|
||||||
name = unicode(item.data(0, NAME_ROLE).toString())
|
name = unicode(item.data(0, NAME_ROLE).toString())
|
||||||
ok = category in {'text', 'styles'}
|
ok = category in {'text', 'styles'}
|
||||||
if ok:
|
if ok:
|
||||||
ans[category].append(name)
|
ans[category][name] = syntax_from_mime(mime)
|
||||||
if not ok and category == 'misc':
|
if not ok and category == 'misc':
|
||||||
ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')}
|
ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')}
|
||||||
if ok and item.isSelected():
|
if ok and item.isSelected():
|
||||||
ans['selected'].append(name)
|
ans['selected'][name] = syntax_from_mime(mime)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
class FileListWidget(QWidget):
|
class FileListWidget(QWidget):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user