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>'
|
||||
|
||||
import tempfile, shutil, sys, os
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
|
||||
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)
|
||||
|
||||
from calibre import prints
|
||||
@ -272,7 +273,7 @@ class Boss(QObject):
|
||||
# Ensure the search panel is visible
|
||||
sp.setVisible(True)
|
||||
ed = self.gui.central.current_editor
|
||||
name = None
|
||||
name = editor = None
|
||||
for n, x in editors.iteritems():
|
||||
if x is ed:
|
||||
name = n
|
||||
@ -295,47 +296,74 @@ class Boss(QObject):
|
||||
if err:
|
||||
return error_dialog(self.gui, _('Cannot search'), err, show=True)
|
||||
del err
|
||||
|
||||
files = OrderedDict()
|
||||
do_all = state['wrap'] or action in {'replace-all', 'count'}
|
||||
marked = False
|
||||
if where == 'current':
|
||||
files = [name]
|
||||
editor = ed
|
||||
elif where in {'styles', 'text', 'selected'}:
|
||||
files = searchable_names[where]
|
||||
if name in files:
|
||||
# Start searching in the current editor
|
||||
editor = ed
|
||||
else:
|
||||
common = set(editors).intersection(set(files))
|
||||
if common:
|
||||
name = next(x for x in files if x in common)
|
||||
editor = editors[name]
|
||||
self.gui.central.show_editor(editor)
|
||||
# Re-order the list of other files so that we search in the same
|
||||
# order every time. Depending on direction, search the files
|
||||
# that come after the current file, or before the current file,
|
||||
# first.
|
||||
lfiles = list(files)
|
||||
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:
|
||||
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:
|
||||
files = [name]
|
||||
pass # marked text TODO: Implement this
|
||||
editor = ed
|
||||
marked = True
|
||||
|
||||
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(
|
||||
self.gui, _('Not found'), _(
|
||||
'No matches were found for %s') % state['find'], show=True)
|
||||
self.gui, _('Not found'), msg, show=True)
|
||||
|
||||
pat = sp.get_regex(state)
|
||||
|
||||
def do_find():
|
||||
found = editor.find(pat)
|
||||
if found:
|
||||
return
|
||||
if len(files) == 1:
|
||||
if not state['wrap']:
|
||||
return no_match()
|
||||
found = editor.find(pat, wrap=True)
|
||||
if not found:
|
||||
return no_match()
|
||||
else:
|
||||
pass # TODO: handle multiple file search
|
||||
if editor is not None:
|
||||
if editor.find(pat, marked=marked):
|
||||
return
|
||||
if not files:
|
||||
if not state['wrap']:
|
||||
return no_match()
|
||||
return editor.find(pat, wrap=True, marked=marked) or no_match()
|
||||
for fname, syntax in files.iteritems():
|
||||
if fname in editors:
|
||||
if not editors[fname].find(pat, complete=True):
|
||||
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':
|
||||
return do_find()
|
||||
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
try:
|
||||
if action == 'find':
|
||||
return do_find()
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def save_book(self):
|
||||
c = current_container()
|
||||
|
@ -154,12 +154,59 @@ class TextEdit(QPlainTextEdit):
|
||||
self.current_search_mark = None
|
||||
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
|
||||
c = self.textCursor()
|
||||
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:
|
||||
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
|
||||
c.movePosition(pos, c.KeepAnchor)
|
||||
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
|
||||
@ -169,7 +216,7 @@ class TextEdit(QPlainTextEdit):
|
||||
start, end = m.span()
|
||||
if start == end:
|
||||
return False
|
||||
if wrap:
|
||||
if wrap and not complete:
|
||||
if reverse:
|
||||
textpos = c.anchor()
|
||||
start, end = textpos + end, textpos + start
|
||||
|
@ -7,6 +7,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
from binascii import hexlify
|
||||
from collections import OrderedDict
|
||||
from PyQt4.Qt import (
|
||||
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon,
|
||||
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.gui2 import error_dialog
|
||||
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
|
||||
|
||||
TOP_ICON_SIZE = 24
|
||||
@ -344,18 +346,18 @@ class FileList(QTreeWidget):
|
||||
|
||||
@property
|
||||
def searchable_names(self):
|
||||
ans = {'text':[], 'styles':[], 'selected':[]}
|
||||
ans = {'text':OrderedDict(), 'styles':OrderedDict(), 'selected':OrderedDict()}
|
||||
for item in self.all_files:
|
||||
category = unicode(item.data(0, CATEGORY_ROLE).toString())
|
||||
mime = unicode(item.data(0, MIME_ROLE).toString())
|
||||
name = unicode(item.data(0, NAME_ROLE).toString())
|
||||
ok = category in {'text', 'styles'}
|
||||
if ok:
|
||||
ans[category].append(name)
|
||||
ans[category][name] = syntax_from_mime(mime)
|
||||
if not ok and category == 'misc':
|
||||
ok = mime in {guess_type('a.'+x) for x in ('opf', 'ncx', 'txt', 'xml')}
|
||||
if ok and item.isSelected():
|
||||
ans['selected'].append(name)
|
||||
ans['selected'][name] = syntax_from_mime(mime)
|
||||
return ans
|
||||
|
||||
class FileListWidget(QWidget):
|
||||
|
Loading…
x
Reference in New Issue
Block a user