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