Start work on the search panel

This commit is contained in:
Kovid Goyal 2013-11-10 14:18:18 +05:30
parent a278901903
commit 5fd300a533
2 changed files with 276 additions and 3 deletions

View 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)
# }}}

View File

@ -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()