2016-11-25 01:29:55 +00:00
|
|
|
#!/usr/bin/env python2
|
2009-08-18 12:43:18 +00:00
|
|
|
# Terminator by Chris Jones <cmsj@tenshu.net>
|
|
|
|
# GPL v2 only
|
|
|
|
"""searchbar.py - classes necessary to provide a terminal search bar"""
|
|
|
|
|
2015-06-21 02:23:52 +00:00
|
|
|
from gi.repository import Gtk, Gdk
|
2014-09-19 14:08:08 +00:00
|
|
|
from gi.repository import GObject
|
2011-03-22 07:05:38 +00:00
|
|
|
import re
|
2009-08-18 12:43:18 +00:00
|
|
|
|
|
|
|
from translation import _
|
2009-11-04 18:06:34 +00:00
|
|
|
from config import Config
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-08-18 12:51:09 +00:00
|
|
|
# pylint: disable-msg=R0904
|
2014-09-19 14:08:08 +00:00
|
|
|
class Searchbar(Gtk.HBox):
|
2009-08-18 12:43:18 +00:00
|
|
|
"""Class implementing the Searchbar widget"""
|
|
|
|
|
|
|
|
__gsignals__ = {
|
2014-09-19 14:08:08 +00:00
|
|
|
'end-search': (GObject.SignalFlags.RUN_LAST, None, ()),
|
2009-08-18 12:43:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
entry = None
|
|
|
|
reslabel = None
|
|
|
|
next = None
|
2009-11-04 18:06:34 +00:00
|
|
|
prev = None
|
2015-06-21 15:57:14 +00:00
|
|
|
wrap = None
|
2009-11-04 18:06:34 +00:00
|
|
|
|
|
|
|
vte = None
|
|
|
|
config = None
|
|
|
|
|
|
|
|
searchstring = None
|
2011-03-22 07:05:38 +00:00
|
|
|
searchre = None
|
2009-11-04 18:06:34 +00:00
|
|
|
searchrow = None
|
|
|
|
|
|
|
|
searchits = None
|
2009-08-18 12:43:18 +00:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Class initialiser"""
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.GObject.__init__(self)
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
self.config = Config()
|
|
|
|
|
2009-08-18 12:43:18 +00:00
|
|
|
# Search text
|
2014-09-19 14:08:08 +00:00
|
|
|
self.entry = Gtk.Entry()
|
2009-08-18 12:43:18 +00:00
|
|
|
self.entry.set_activates_default(True)
|
|
|
|
self.entry.show()
|
|
|
|
self.entry.connect('activate', self.do_search)
|
|
|
|
self.entry.connect('key-press-event', self.search_keypress)
|
|
|
|
|
|
|
|
# Label
|
2014-09-19 14:08:08 +00:00
|
|
|
label = Gtk.Label(label=_('Search:'))
|
2009-08-18 12:43:18 +00:00
|
|
|
label.show()
|
|
|
|
|
|
|
|
# Result label
|
2014-09-19 14:08:08 +00:00
|
|
|
self.reslabel = Gtk.Label(label='')
|
2009-08-18 12:43:18 +00:00
|
|
|
self.reslabel.show()
|
|
|
|
|
|
|
|
# Close Button
|
2014-09-19 14:08:08 +00:00
|
|
|
close = Gtk.Button()
|
|
|
|
close.set_relief(Gtk.ReliefStyle.NONE)
|
2009-08-18 12:43:18 +00:00
|
|
|
close.set_focus_on_click(False)
|
2014-09-19 14:08:08 +00:00
|
|
|
icon = Gtk.Image()
|
|
|
|
icon.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)
|
2009-08-18 12:43:18 +00:00
|
|
|
close.add(icon)
|
|
|
|
close.set_name('terminator-search-close-button')
|
|
|
|
if hasattr(close, 'set_tooltip_text'):
|
|
|
|
close.set_tooltip_text(_('Close Search bar'))
|
|
|
|
close.connect('clicked', self.end_search)
|
|
|
|
close.show_all()
|
|
|
|
|
|
|
|
# Next Button
|
2014-09-19 14:08:08 +00:00
|
|
|
self.next = Gtk.Button(_('Next'))
|
2009-11-04 20:41:01 +00:00
|
|
|
self.next.show()
|
|
|
|
self.next.set_sensitive(False)
|
2009-08-18 12:43:18 +00:00
|
|
|
self.next.connect('clicked', self.next_search)
|
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
# Previous Button
|
2014-09-19 14:08:08 +00:00
|
|
|
self.prev = Gtk.Button(_('Prev'))
|
2009-11-04 20:41:01 +00:00
|
|
|
self.prev.show()
|
|
|
|
self.prev.set_sensitive(False)
|
2009-11-04 18:06:34 +00:00
|
|
|
self.prev.connect('clicked', self.prev_search)
|
|
|
|
|
2015-06-21 15:57:14 +00:00
|
|
|
# Wrap checkbox
|
|
|
|
self.wrap = Gtk.CheckButton(_('Wrap'))
|
|
|
|
self.wrap.show()
|
|
|
|
self.wrap.set_sensitive(True)
|
|
|
|
self.wrap.connect('toggled', self.wrap_toggled)
|
|
|
|
|
2014-09-19 14:10:43 +00:00
|
|
|
self.pack_start(label, False, True, 0)
|
2014-09-19 14:08:08 +00:00
|
|
|
self.pack_start(self.entry, True, True, 0)
|
2014-09-19 14:10:43 +00:00
|
|
|
self.pack_start(self.reslabel, False, True, 0)
|
|
|
|
self.pack_start(self.prev, False, False, 0)
|
|
|
|
self.pack_start(self.next, False, False, 0)
|
2015-06-21 15:57:14 +00:00
|
|
|
self.pack_start(self.wrap, False, False, 0)
|
2014-09-19 14:10:43 +00:00
|
|
|
self.pack_end(close, False, False, 0)
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-08-19 18:00:33 +00:00
|
|
|
self.hide()
|
2009-11-07 01:40:43 +00:00
|
|
|
self.set_no_show_all(True)
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2015-06-21 15:57:14 +00:00
|
|
|
def wrap_toggled(self, toggled):
|
|
|
|
if self.searchrow is None:
|
|
|
|
self.prev.set_sensitive(False)
|
|
|
|
self.next.set_sensitive(False)
|
|
|
|
elif toggled:
|
|
|
|
self.prev.set_sensitive(True)
|
|
|
|
self.next.set_sensitive(True)
|
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
def get_vte(self):
|
|
|
|
"""Find our parent widget"""
|
|
|
|
parent = self.get_parent()
|
|
|
|
if parent:
|
|
|
|
self.vte = parent.vte
|
|
|
|
|
2009-08-18 12:51:09 +00:00
|
|
|
# pylint: disable-msg=W0613
|
2009-08-18 12:43:18 +00:00
|
|
|
def search_keypress(self, widget, event):
|
2009-08-18 12:48:04 +00:00
|
|
|
"""Handle keypress events"""
|
2014-09-19 14:08:08 +00:00
|
|
|
key = Gdk.keyval_name(event.keyval)
|
2009-08-18 12:48:04 +00:00
|
|
|
if key == 'Escape':
|
|
|
|
self.end_search()
|
2015-06-21 15:57:14 +00:00
|
|
|
else:
|
|
|
|
self.prev.set_sensitive(False)
|
|
|
|
self.next.set_sensitive(False)
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
def start_search(self):
|
|
|
|
"""Show ourselves"""
|
|
|
|
if not self.vte:
|
|
|
|
self.get_vte()
|
|
|
|
|
|
|
|
self.show()
|
|
|
|
self.entry.grab_focus()
|
|
|
|
|
2009-08-18 12:43:18 +00:00
|
|
|
def do_search(self, widget):
|
|
|
|
"""Trap and re-emit the clicked signal"""
|
2009-11-04 18:06:34 +00:00
|
|
|
searchtext = self.entry.get_text()
|
|
|
|
if searchtext == '':
|
|
|
|
return
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
if searchtext != self.searchstring:
|
2015-06-21 15:57:14 +00:00
|
|
|
self.searchrow = self.get_vte_buffer_range()[0] - 1
|
2009-11-04 18:06:34 +00:00
|
|
|
self.searchstring = searchtext
|
2011-03-22 07:05:38 +00:00
|
|
|
self.searchre = re.compile(searchtext)
|
2009-08-18 12:43:18 +00:00
|
|
|
|
2009-11-04 18:06:34 +00:00
|
|
|
self.reslabel.set_text(_("Searching scrollback"))
|
2009-11-04 20:41:01 +00:00
|
|
|
self.next.set_sensitive(True)
|
|
|
|
self.prev.set_sensitive(True)
|
2009-11-04 18:06:34 +00:00
|
|
|
self.next_search(None)
|
|
|
|
|
|
|
|
def next_search(self, widget):
|
|
|
|
"""Search forwards and jump to the next result, if any"""
|
|
|
|
startrow,endrow = self.get_vte_buffer_range()
|
2015-06-21 15:57:14 +00:00
|
|
|
found = startrow <= self.searchrow and self.searchrow < endrow
|
|
|
|
row = self.searchrow
|
2009-11-04 18:06:34 +00:00
|
|
|
while True:
|
2015-06-21 15:57:14 +00:00
|
|
|
row += 1
|
|
|
|
if row >= endrow:
|
|
|
|
if found and self.wrap.get_active():
|
|
|
|
row = startrow - 1
|
|
|
|
else:
|
|
|
|
self.prev.set_sensitive(found)
|
|
|
|
self.next.set_sensitive(False)
|
|
|
|
self.reslabel.set_text(_('No more results'))
|
|
|
|
return
|
|
|
|
buffer = self.vte.get_text_range(row, 0, row + 1, 0, self.search_character)
|
2009-11-04 18:06:34 +00:00
|
|
|
|
2015-06-21 02:23:52 +00:00
|
|
|
buffer = buffer[0]
|
|
|
|
buffer = buffer[:buffer.find('\n')]
|
2011-03-22 07:05:38 +00:00
|
|
|
matches = self.searchre.search(buffer)
|
|
|
|
if matches:
|
2015-06-21 15:57:14 +00:00
|
|
|
self.searchrow = row
|
|
|
|
self.prev.set_sensitive(True)
|
2009-11-04 18:06:34 +00:00
|
|
|
self.search_hit(self.searchrow)
|
|
|
|
return
|
|
|
|
|
|
|
|
def prev_search(self, widget):
|
|
|
|
"""Jump back to the previous search"""
|
2009-11-04 20:41:01 +00:00
|
|
|
startrow,endrow = self.get_vte_buffer_range()
|
2015-06-21 15:57:14 +00:00
|
|
|
found = startrow <= self.searchrow and self.searchrow < endrow
|
|
|
|
row = self.searchrow
|
2009-11-04 20:41:01 +00:00
|
|
|
while True:
|
2015-06-21 15:57:14 +00:00
|
|
|
row -= 1
|
|
|
|
if row <= startrow:
|
|
|
|
if found and self.wrap.get_active():
|
|
|
|
row = endrow
|
|
|
|
else:
|
|
|
|
self.next.set_sensitive(found)
|
|
|
|
self.prev.set_sensitive(False)
|
|
|
|
self.reslabel.set_text(_('No more results'))
|
|
|
|
return
|
|
|
|
buffer = self.vte.get_text_range(row, 0, row + 1, 0, self.search_character)
|
2009-11-04 20:41:01 +00:00
|
|
|
|
2015-06-21 02:23:52 +00:00
|
|
|
buffer = buffer[0]
|
|
|
|
buffer = buffer[:buffer.find('\n')]
|
2011-03-22 07:05:38 +00:00
|
|
|
matches = self.searchre.search(buffer)
|
|
|
|
if matches:
|
2015-06-21 15:57:14 +00:00
|
|
|
self.searchrow = row
|
|
|
|
self.next.set_sensitive(True)
|
2009-11-04 20:41:01 +00:00
|
|
|
self.search_hit(self.searchrow)
|
|
|
|
return
|
2009-11-04 18:06:34 +00:00
|
|
|
|
|
|
|
def search_hit(self, row):
|
|
|
|
"""Update the UI for a search hit"""
|
|
|
|
self.reslabel.set_text("%s %d" % (_('Found at row'), row))
|
|
|
|
self.get_parent().scrollbar_jump(row)
|
|
|
|
self.next.show()
|
2009-11-04 20:41:01 +00:00
|
|
|
self.prev.show()
|
2009-11-04 18:06:34 +00:00
|
|
|
|
2015-06-21 02:23:52 +00:00
|
|
|
def search_character(self, widget, col, row):
|
2009-11-04 18:06:34 +00:00
|
|
|
"""We have to have a callback for each character"""
|
|
|
|
return(True)
|
|
|
|
|
|
|
|
def get_vte_buffer_range(self):
|
|
|
|
"""Get the range of a vte widget"""
|
|
|
|
column, endrow = self.vte.get_cursor_position()
|
2011-04-08 22:32:52 +00:00
|
|
|
if self.config['scrollback_lines'] < 0:
|
|
|
|
startrow = 0
|
|
|
|
else:
|
|
|
|
startrow = max(0, endrow - self.config['scrollback_lines'])
|
2009-11-04 18:06:34 +00:00
|
|
|
return(startrow, endrow)
|
|
|
|
|
|
|
|
def end_search(self, widget=None):
|
2009-08-18 12:43:18 +00:00
|
|
|
"""Trap and re-emit the end-search signal"""
|
2009-11-04 18:06:34 +00:00
|
|
|
self.searchrow = 0
|
|
|
|
self.searchstring = None
|
2011-03-22 07:05:38 +00:00
|
|
|
self.searchre = None
|
2009-11-04 18:06:34 +00:00
|
|
|
self.reslabel.set_text('')
|
|
|
|
self.emit('end-search')
|
2009-08-18 12:43:18 +00:00
|
|
|
|
|
|
|
def get_search_term(self):
|
|
|
|
"""Return the currently set search term"""
|
|
|
|
return(self.entry.get_text())
|
|
|
|
|
2014-09-19 14:08:08 +00:00
|
|
|
GObject.type_register(Searchbar)
|