mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Start work on the search panel
This commit is contained in:
parent
a278901903
commit
5fd300a533
252
src/calibre/gui2/tweak_book/search.py
Normal file
252
src/calibre/gui2/tweak_book/search.py
Normal file
@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QWidget, QToolBar, Qt, QHBoxLayout, QSize, QIcon, QGridLayout, QLabel,
|
||||
QPushButton, pyqtSignal, QComboBox, QCheckBox, QSizePolicy)
|
||||
|
||||
import regex
|
||||
|
||||
from calibre.gui2.widgets import HistoryLineEdit
|
||||
from calibre.gui2.tweak_book import tprefs
|
||||
|
||||
REGEX_FLAGS = regex.VERSION1 | regex.WORD | regex.FULLCASE | regex.MULTILINE | regex.UNICODE
|
||||
|
||||
# The search panel {{{
|
||||
|
||||
class PushButton(QPushButton):
|
||||
|
||||
triggered = pyqtSignal(object)
|
||||
|
||||
def __init__(self, text, action, parent):
|
||||
QPushButton.__init__(self, text, parent)
|
||||
self.clicked.connect(lambda : self.triggered.emit(action))
|
||||
|
||||
class SearchWidget(QWidget):
|
||||
|
||||
DEFAULT_STATE = {
|
||||
'mode': 'normal',
|
||||
'where': 'current',
|
||||
'case_sensitive': False,
|
||||
'direction': 'down',
|
||||
'wrap': True,
|
||||
'dot_all': False,
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QGridLayout(self)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(l)
|
||||
|
||||
self.fl = fl = QLabel(_('&Find:'))
|
||||
fl.setAlignment(Qt.AlignRight | Qt.AlignCenter)
|
||||
self.find_text = ft = HistoryLineEdit(self)
|
||||
ft.initialize('tweak_book_find_edit')
|
||||
fl.setBuddy(ft)
|
||||
l.addWidget(fl, 0, 0)
|
||||
l.addWidget(ft, 0, 1)
|
||||
|
||||
self.rl = rl = QLabel(_('&Replace:'))
|
||||
rl.setAlignment(Qt.AlignRight | Qt.AlignCenter)
|
||||
self.replace_text = rt = HistoryLineEdit(self)
|
||||
rt.initialize('tweak_book_replace_edit')
|
||||
rl.setBuddy(rt)
|
||||
l.addWidget(rl, 1, 0)
|
||||
l.addWidget(rt, 1, 1)
|
||||
l.setColumnStretch(1, 10)
|
||||
|
||||
self.fb = fb = PushButton(_('&Find'), 'find', self)
|
||||
self.rfb = rfb = PushButton(_('Replace a&nd Find'), 'replace-find', self)
|
||||
self.rb = rb = PushButton(_('&Replace'), 'replace', self)
|
||||
self.rab = rab = PushButton(_('Replace &all'), 'replace-all', self)
|
||||
l.addWidget(fb, 0, 2)
|
||||
l.addWidget(rfb, 0, 3)
|
||||
l.addWidget(rb, 1, 2)
|
||||
l.addWidget(rab, 1, 3)
|
||||
|
||||
self.ml = ml = QLabel(_('&Mode:'))
|
||||
self.ol = ol = QHBoxLayout()
|
||||
ml.setAlignment(Qt.AlignRight | Qt.AlignCenter)
|
||||
l.addWidget(ml, 2, 0)
|
||||
l.addLayout(ol, 2, 1, 1, 3)
|
||||
self.mode_box = mb = QComboBox(self)
|
||||
mb.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
mb.addItems([_('Normal'), _('Regex')])
|
||||
mb.setToolTip('<style>dd {margin-bottom: 1.5ex}</style>' + _(
|
||||
'''Select how the search expression is interpreted
|
||||
<dl>
|
||||
<dt><b>Normal</b></dt>
|
||||
<dd>The search expression is treated as normal text, calibre will look for the exact text.</dd>
|
||||
<dt><b>Regex</b></dt>
|
||||
<dd>The search expression is interpreted as a regular expression. See the User Manual for more help on using regular expressions.</dd>
|
||||
</dl>'''))
|
||||
ml.setBuddy(mb)
|
||||
ol.addWidget(mb)
|
||||
|
||||
self.where_box = wb = QComboBox(self)
|
||||
wb.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
wb.addItems([_('Current file'), _('All text files'), _('All style files'), _('Selected files'), _('Selected text')])
|
||||
wb.setToolTip('<style>dd {margin-bottom: 1.5ex}</style>' + _(
|
||||
'''
|
||||
Where to search/replace:
|
||||
<dl>
|
||||
<dt><b>Current file</b></dt>
|
||||
<dd>Search only inside the currently opened file</dd>
|
||||
<dt><b>All text files</b></dt>
|
||||
<dd>Search in all text (HTML) files</dd>
|
||||
<dt><b>All style files</b></dt>
|
||||
<dd>Search in all style (CSS) files</dd>
|
||||
<dt><b>Selected files</b></dt>
|
||||
<dd>Search in the files currently selected in the Files Browser</dd>
|
||||
<dt><b>Selected text</b></dt>
|
||||
<dd>Search only within the selected text in the currently opened file</dd>
|
||||
</dl>'''))
|
||||
ol.addWidget(wb)
|
||||
|
||||
self.direction_box = db = QComboBox(self)
|
||||
db.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
db.addItems([_('Down'), _('Up')])
|
||||
db.setToolTip('<style>dd {margin-bottom: 1.5ex}</style>' + _(
|
||||
'''
|
||||
Direction to search:
|
||||
<dl>
|
||||
<dt><b>Down</b></dt>
|
||||
<dd>Search for the next match from your current position</dd>
|
||||
<dt><b>Up</b></dt>
|
||||
<dd>Search for the previous match from your current position</dd>
|
||||
</dl>'''))
|
||||
ol.addWidget(db)
|
||||
|
||||
self.cs = cs = QCheckBox(_('&Case sensitive'))
|
||||
cs.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
ol.addWidget(cs)
|
||||
|
||||
self.wr = wr = QCheckBox(_('&Wrap'))
|
||||
wr.setToolTip('<p>'+_('When searching reaches the end, wrap around to the beginning and continue the search'))
|
||||
wr.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
ol.addWidget(wr)
|
||||
|
||||
self.da = da = QCheckBox(_('&Dot all'))
|
||||
da.setToolTip('<p>'+_("Make the '.' special character match any character at all, including a newline"))
|
||||
da.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
|
||||
ol.addWidget(da)
|
||||
|
||||
self.mode_box.currentIndexChanged[int].connect(self.da.setVisible)
|
||||
|
||||
ol.addStretch(10)
|
||||
|
||||
@dynamic_property
|
||||
def mode(self):
|
||||
def fget(self):
|
||||
return 'normal' if self.mode_box.currentIndex() == 0 else 'regex'
|
||||
def fset(self, val):
|
||||
self.mode_box.setCurrentIndex({'regex':1}.get(val, 0))
|
||||
self.da.setVisible(self.mode == 'regex')
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def find(self):
|
||||
def fget(self):
|
||||
return unicode(self.find_text.text()).strip()
|
||||
def fset(self, val):
|
||||
self.find_text.setText(val)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def replace(self):
|
||||
def fget(self):
|
||||
return unicode(self.replace_text.text()).strip()
|
||||
def fset(self, val):
|
||||
self.replace_text.setText(val)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def where(self):
|
||||
wm = {0:'current', 1:'text', 2:'style', 3:'selected-files', 4:'selected-text'}
|
||||
def fget(self):
|
||||
return wm[self.where_box.currentIndex()]
|
||||
def fset(self, val):
|
||||
self.where_box.setCurrentIndex({v:k for k, v in wm.iteritems()}[val])
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def case_sensitive(self):
|
||||
def fget(self):
|
||||
return self.cs.isChecked()
|
||||
def fset(self, val):
|
||||
self.cs.setChecked(bool(val))
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def direction(self):
|
||||
def fget(self):
|
||||
return 'down' if self.direction_box.currentIndex() == 0 else 'up'
|
||||
def fset(self, val):
|
||||
self.direction_box.setCurrentIndex(1 if val == 'up' else 0)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def wrap(self):
|
||||
def fget(self):
|
||||
return self.wr.isChecked()
|
||||
def fset(self, val):
|
||||
self.wr.setChecked(bool(val))
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def dot_all(self):
|
||||
def fget(self):
|
||||
return self.da.isChecked()
|
||||
def fset(self, val):
|
||||
self.da.setChecked(bool(val))
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@dynamic_property
|
||||
def state(self):
|
||||
def fget(self):
|
||||
return {x:getattr(self, x) for x in self.DEFAULT_STATE}
|
||||
def fset(self, val):
|
||||
for x in self.DEFAULT_STATE:
|
||||
if x in val:
|
||||
setattr(self, x, val[x])
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def restore_state(self):
|
||||
self.state = tprefs.get('find-widget-state', self.DEFAULT_STATE)
|
||||
|
||||
def save_state(self):
|
||||
tprefs.set('find-widget-state', self.state)
|
||||
|
||||
class SearchPanel(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QHBoxLayout()
|
||||
self.setLayout(l)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
self.t = t = QToolBar(self)
|
||||
l.addWidget(t)
|
||||
t.setOrientation(Qt.Vertical)
|
||||
t.setIconSize(QSize(12, 12))
|
||||
t.setMovable(False)
|
||||
t.setFloatable(False)
|
||||
t.cl = ac = t.addAction(QIcon(I('window-close.png')), _('Close search panel'))
|
||||
ac.triggered.connect(self.hide_panel)
|
||||
self.widget = SearchWidget(self)
|
||||
l.addWidget(self.widget)
|
||||
self.restore_state, self.save_state = self.widget.restore_state, self.widget.save_state
|
||||
|
||||
def hide_panel(self):
|
||||
self.setVisible(False)
|
||||
|
||||
def show_panel(self):
|
||||
self.setVisible(True)
|
||||
self.widget.find_text.setFocus(Qt.OtherFocusReason)
|
||||
# }}}
|
||||
|
@ -20,6 +20,7 @@ from calibre.gui2.tweak_book.job import BlockingJob
|
||||
from calibre.gui2.tweak_book.boss import Boss
|
||||
from calibre.gui2.tweak_book.keyboard import KeyboardManager
|
||||
from calibre.gui2.tweak_book.preview import Preview
|
||||
from calibre.gui2.tweak_book.search import SearchPanel
|
||||
|
||||
class Central(QStackedWidget):
|
||||
|
||||
@ -56,6 +57,9 @@ class Central(QStackedWidget):
|
||||
self.modified_icon = QIcon(I('modified.png'))
|
||||
self.editor_tabs.currentChanged.connect(self.current_editor_changed)
|
||||
self.editor_tabs.tabCloseRequested.connect(self._close_requested)
|
||||
self.search_panel = SearchPanel(self)
|
||||
l.addWidget(self.search_panel)
|
||||
self.restore_state()
|
||||
|
||||
def _close_requested(self, index):
|
||||
editor = self.editor_tabs.widget(index)
|
||||
@ -91,6 +95,17 @@ class Central(QStackedWidget):
|
||||
def current_editor(self):
|
||||
return self.editor_tabs.currentWidget()
|
||||
|
||||
def save_state(self):
|
||||
tprefs.set('search-panel-visible', self.search_panel.isVisible())
|
||||
self.search_panel.save_state()
|
||||
|
||||
def restore_state(self):
|
||||
self.search_panel.setVisible(tprefs.get('search-panel-visible', False))
|
||||
self.search_panel.restore_state()
|
||||
|
||||
def show_find(self):
|
||||
self.search_panel.show_panel()
|
||||
|
||||
class Main(MainWindow):
|
||||
|
||||
APP_NAME = _('Tweak Book')
|
||||
@ -108,6 +123,9 @@ class Main(MainWindow):
|
||||
self.blocking_job = BlockingJob(self)
|
||||
self.keyboard = KeyboardManager()
|
||||
|
||||
self.central = Central(self)
|
||||
self.setCentralWidget(self.central)
|
||||
|
||||
self.create_actions()
|
||||
self.create_toolbars()
|
||||
self.create_docks()
|
||||
@ -120,9 +138,6 @@ class Main(MainWindow):
|
||||
f.setBold(True)
|
||||
self.status_bar.setFont(f)
|
||||
|
||||
self.central = Central(self)
|
||||
self.setCentralWidget(self.central)
|
||||
|
||||
self.boss(self)
|
||||
g = QApplication.instance().desktop().availableGeometry(self)
|
||||
self.resize(g.width()-50, g.height()-50)
|
||||
@ -199,6 +214,10 @@ class Main(MainWindow):
|
||||
self.action_auto_reload_preview = reg('auto-reload.png', _('Auto reload preview'), None, 'auto-reload-preview', (), _('Auto reload preview'))
|
||||
self.action_reload_preview = reg('view-refresh.png', _('Refresh preview'), None, 'reload-preview', ('F5', 'Ctrl+R'), _('Refresh preview'))
|
||||
|
||||
# Search actions
|
||||
group = _('Search')
|
||||
self.action_find = reg('search.png', _('&Find/Replace'), self.central.show_find, 'find-replace', ('Ctrl+F',), _('Find/Replace'))
|
||||
|
||||
def create_menubar(self):
|
||||
b = self.menuBar()
|
||||
|
||||
@ -303,6 +322,7 @@ class Main(MainWindow):
|
||||
def save_state(self):
|
||||
tprefs.set('main_window_geometry', bytearray(self.saveGeometry()))
|
||||
tprefs.set('main_window_state', bytearray(self.saveState(self.STATE_VERSION)))
|
||||
self.central.save_state()
|
||||
|
||||
def restore_state(self):
|
||||
geom = tprefs.get('main_window_geometry', None)
|
||||
@ -311,6 +331,7 @@ class Main(MainWindow):
|
||||
state = tprefs.get('main_window_state', None)
|
||||
if state is not None:
|
||||
self.restoreState(state, self.STATE_VERSION)
|
||||
self.central.restore_state()
|
||||
# We never want to start with the inspector showing
|
||||
self.inspector_dock.close()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user