1### Copyright (C) 2002-2009 Stephen Kennedy <stevek@gnome.org> 2### Copyright (C) 2012 Kai Willadsen <kai.willadsen@gmail.com> 3 4### This program is free software; you can redistribute it and/or modify 5### it under the terms of the GNU General Public License as published by 6### the Free Software Foundation; either version 2 of the License, or 7### (at your option) any later version. 8 9### This program is distributed in the hope that it will be useful, 10### but WITHOUT ANY WARRANTY; without even the implied warranty of 11### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12### GNU General Public License for more details. 13 14### You should have received a copy of the GNU General Public License 15### along with this program; if not, write to the Free Software 16### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 17### USA. 18 19import gtk 20import re 21 22from meld import misc 23from meld import paths 24from . import gnomeglade 25 26from gettext import gettext as _ 27 28class FindBar(gnomeglade.Component): 29 def __init__(self, parent): 30 gnomeglade.Component.__init__(self, paths.ui_dir("findbar.ui"), 31 "findbar", ["arrow_left", "arrow_right"]) 32 gnomeglade.connect_signal_handlers(self) 33 self.textview = None 34 self.orig_base_color = self.find_entry.get_style().base[0] 35 self.arrow_left.show() 36 self.arrow_right.show() 37 parent.connect('set-focus-child', self.on_focus_child) 38 39 def on_focus_child(self, container, widget): 40 if widget is not None: 41 # TODO: Not in PyGtk 2.16; remove this check later 42 if hasattr(self.widget, "get_visible"): 43 visible = self.widget.get_visible() 44 else: 45 visible = self.widget.props.visible 46 if widget is not self.widget and visible: 47 self.hide() 48 return False 49 50 def hide(self): 51 self.textview = None 52 self.wrap_box.hide() 53 self.widget.hide() 54 55 def start_find(self, textview, text=None): 56 self.textview = textview 57 self.replace_label.hide() 58 self.replace_entry.hide() 59 self.hbuttonbox2.hide() 60 if text: 61 self.find_entry.set_text(text) 62 self.widget.set_row_spacings(0) 63 self.widget.show() 64 self.find_entry.grab_focus() 65 66 def start_find_next(self, textview): 67 self.textview = textview 68 if self.find_entry.get_text(): 69 self.find_next_button.activate() 70 else: 71 self.start_find(self.textview) 72 73 def start_find_previous(self, textview, text=None): 74 self.textview = textview 75 if self.find_entry.get_text(): 76 self.find_previous_button.activate() 77 else: 78 self.start_find(self.textview) 79 80 def start_replace(self, textview, text=None): 81 self.textview = textview 82 if text: 83 self.find_entry.set_text(text) 84 self.widget.set_row_spacings(6) 85 self.widget.show_all() 86 self.find_entry.grab_focus() 87 self.wrap_box.hide() 88 89 def on_find_entry__activate(self, entry): 90 self.find_next_button.activate() 91 92 def on_replace_entry__activate(self, entry): 93 self.replace_button.activate() 94 95 def on_find_next_button__clicked(self, button): 96 self._find_text() 97 98 def on_find_previous_button__clicked(self, button): 99 self._find_text(backwards=True) 100 101 def on_replace_button__clicked(self, entry): 102 buf = self.textview.get_buffer() 103 oldsel = buf.get_selection_bounds() 104 match = self._find_text(0) 105 newsel = buf.get_selection_bounds() 106 # only replace if there is a match at the cursor and it was already selected 107 if match and oldsel and oldsel[0].equal(newsel[0]) and oldsel[1].equal(newsel[1]): 108 buf.begin_user_action() 109 buf.delete_selection(False,False) 110 buf.insert_at_cursor( self.replace_entry.get_text() ) 111 self._find_text( 0 ) 112 buf.end_user_action() 113 114 def on_replace_all_button__clicked(self, entry): 115 buf = self.textview.get_buffer() 116 saved_insert = buf.create_mark(None, buf.get_iter_at_mark(buf.get_insert()), True) 117 buf.begin_user_action() 118 while self._find_text(0): 119 buf.delete_selection(False,False) 120 buf.insert_at_cursor( self.replace_entry.get_text() ) 121 buf.end_user_action() 122 if not saved_insert.get_deleted(): 123 buf.place_cursor( buf.get_iter_at_mark(saved_insert) ) 124 self.textview.scroll_to_mark(buf.get_insert(), 0.25) 125 126 def on_find_entry__changed(self, entry): 127 entry.modify_base( gtk.STATE_NORMAL, self.orig_base_color ) 128 129 # 130 # find/replace buffer 131 # 132 def _find_text(self, start_offset=1, backwards=False, wrap=True): 133 match_case = self.match_case.get_active() 134 whole_word = self.whole_word.get_active() 135 regex = self.regex.get_active() 136 assert self.textview 137 buf = self.textview.get_buffer() 138 insert = buf.get_iter_at_mark( buf.get_insert() ) 139 tofind_utf8 = self.find_entry.get_text() 140 tofind = tofind_utf8.decode("utf-8") # tofind is utf-8 encoded 141 start, end = buf.get_bounds() 142 text = buf.get_text(start, end, False).decode("utf-8") # as is buffer 143 if not regex: 144 tofind = re.escape(tofind) 145 if whole_word: 146 tofind = r'\b' + tofind + r'\b' 147 try: 148 pattern = re.compile( tofind, (match_case and re.M or (re.M|re.I)) ) 149 except re.error as e: 150 misc.run_dialog( _("Regular expression error\n'%s'") % e, self, messagetype=gtk.MESSAGE_ERROR) 151 else: 152 self.wrap_box.hide() 153 if backwards == False: 154 match = pattern.search(text, insert.get_offset() + start_offset) 155 if match is None and wrap: 156 self.wrap_box.show() 157 match = pattern.search(text, 0) 158 else: 159 match = None 160 for m in pattern.finditer(text, 0, insert.get_offset()): 161 match = m 162 if match is None and wrap: 163 self.wrap_box.show() 164 for m in pattern.finditer(text, insert.get_offset()): 165 match = m 166 if match: 167 it = buf.get_iter_at_offset( match.start() ) 168 buf.place_cursor( it ) 169 it.forward_chars( match.end() - match.start() ) 170 buf.move_mark( buf.get_selection_bound(), it ) 171 self.textview.scroll_to_mark(buf.get_insert(), 0.25) 172 return True 173 else: 174 buf.place_cursor( buf.get_iter_at_mark(buf.get_insert()) ) 175 self.find_entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffdddd")) 176 self.wrap_box.hide() 177