root/trunk/src/message_textview.py

Revision 10652, 10.8 kB (checked in by asterix, 2 weeks ago)

fix html tag handling. Fixes #4480

Line 
1# -*- coding:utf-8 -*-
2## src/message_textview.py
3##
4## Copyright (C) 2003-2007 Yann Leboulanger <asterix AT lagaule.org>
5## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
6## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
7##
8## This file is part of Gajim.
9##
10## Gajim is free software; you can redistribute it and/or modify
11## it under the terms of the GNU General Public License as published
12## by the Free Software Foundation; version 3 only.
13##
14## Gajim is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17## GNU General Public License for more details.
18##
19## You should have received a copy of the GNU General Public License
20## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
21##
22
23import gtk
24import gobject
25import pango
26import gtkgui_helpers
27from common import gajim
28
29class MessageTextView(gtk.TextView):
30        '''Class for the message textview (where user writes new messages)
31        for chat/groupchat windows'''
32        __gsignals__ = dict(
33                mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
34                                None, # return value
35                                (int, gtk.gdk.ModifierType ) # arguments
36                        )
37                )
38               
39        def __init__(self):
40                gtk.TextView.__init__(self)
41
42                # set properties
43                self.set_border_width(1)
44                self.set_accepts_tab(True)
45                self.set_editable(True)
46                self.set_cursor_visible(True)
47                self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
48                self.set_left_margin(2)
49                self.set_right_margin(2)
50                self.set_pixels_above_lines(2)
51                self.set_pixels_below_lines(2)
52
53                self.lang = None # Lang used for spell checking
54                buffer = self.get_buffer()
55                self.begin_tags = {}
56                self.end_tags = {}
57                self.color_tags = []
58                self.fonts_tags = []
59                self.other_tags = {}           
60                self.other_tags['bold'] = buffer.create_tag('bold')
61                self.other_tags['bold'].set_property('weight', pango.WEIGHT_BOLD)
62                self.begin_tags['bold'] = '<strong>'
63                self.end_tags['bold'] = '</strong>'
64                self.other_tags['italic'] = buffer.create_tag('italic')
65                self.other_tags['italic'].set_property('style', pango.STYLE_ITALIC)
66                self.begin_tags['italic'] = '<em>'
67                self.end_tags['italic'] = '</em>'
68                self.other_tags['underline'] = buffer.create_tag('underline')
69                self.other_tags['underline'].set_property('underline', pango.UNDERLINE_SINGLE)
70                self.begin_tags['underline'] = '<span style="text-decoration: underline;">'
71                self.end_tags['underline'] = '</span>'
72                self.other_tags['strike'] = buffer.create_tag('strike')
73                self.other_tags['strike'].set_property('strikethrough', True)
74                self.begin_tags['strike'] = '<span style="text-decoration: line-through;">'
75                self.end_tags['strike'] = '</span>'
76
77        def make_clickable_urls(self, text):
78                buffer = self.get_buffer()
79
80                start = 0
81                end = 0
82                index = 0
83               
84                new_text = ''
85                iterator = gajim.interface.link_pattern_re.finditer(text)
86                for match in iterator:
87                        start, end = match.span()
88                        url = text[start:end]
89                        if start != 0:
90                                text_before_special_text = text[index:start]
91                        else:
92                                text_before_special_text = ''
93                        end_iter = buffer.get_end_iter()
94                        # we insert normal text
95                        new_text += text_before_special_text + \
96                        '<a href="'+ url +'">' + url + '</a>'
97                               
98                        index = end # update index
99
100                if end < len(text):
101                        new_text += text[end:]
102
103                return new_text # the position after *last* special text
104
105        def get_active_tags(self):
106                buffer = self.get_buffer()
107                return_val = buffer.get_selection_bounds()
108                if return_val: # if sth was selected
109                        start, finish = return_val[0], return_val[1]
110                else:
111                        start, finish = buffer.get_bounds()
112                active_tags = []
113                for tag in start.get_tags():
114                        active_tags.append(tag.get_property('name'))
115                return  active_tags
116
117        def set_tag(self, widget, tag):
118                buffer = self.get_buffer()
119                return_val = buffer.get_selection_bounds()
120                if return_val: # if sth was selected
121                        start, finish = return_val[0], return_val[1]
122                else:
123                        start, finish = buffer.get_bounds()
124                if start.has_tag(self.other_tags[tag]):
125                        buffer.remove_tag_by_name(tag, start, finish)
126                else:
127                        if tag == 'underline':
128                                buffer.remove_tag_by_name('strike', start, finish)
129                        elif tag == 'strike':
130                                buffer.remove_tag_by_name('underline', start, finish)
131                        buffer.apply_tag_by_name(tag, start, finish)           
132
133        def clear_tags(self, widget):
134                buffer = self.get_buffer()
135                return_val = buffer.get_selection_bounds()
136                if return_val: # if sth was selected
137                        start, finish = return_val[0], return_val[1]
138                else:
139                        start, finish = buffer.get_bounds()
140                buffer.remove_all_tags(start, finish)
141
142        def color_set(self, widget, response, color):
143                if response == -6:
144                        widget.destroy()
145                        return
146                buffer = self.get_buffer()
147                color = color.get_current_color()
148                widget.destroy()
149                color_string = gtkgui_helpers.make_color_string(color)
150                tag_name = 'color' + color_string
151                if not tag_name in self.color_tags:
152                        tagColor = buffer.create_tag(tag_name)
153                        tagColor.set_property('foreground', color_string)
154                        self.begin_tags[tag_name] = '<span style="color: ' + color_string + ';">'
155                        self.end_tags[tag_name] = '</span>'
156                        self.color_tags.append(tag_name)
157
158                return_val = buffer.get_selection_bounds()
159                if return_val: # if sth was selected
160                        start, finish = return_val[0], return_val[1]
161                else:
162                        start, finish = buffer.get_bounds()
163
164                for tag in self.color_tags:
165                        buffer.remove_tag_by_name(tag, start, finish)
166
167                buffer.apply_tag_by_name(tag_name, start, finish)
168
169        def font_set(self, widget, response, font):
170                if response == -6:
171                        widget.destroy()
172                        return
173
174                buffer = self.get_buffer()
175
176                font = font.get_font_name()
177                font_desc = pango.FontDescription(font)
178                family = font_desc.get_family()
179                size = font_desc.get_size()
180                size = size / pango.SCALE
181                weight = font_desc.get_weight()
182                style = font_desc.get_style()
183
184                widget.destroy()
185
186                tag_name = 'font' + font
187                if not tag_name in self.fonts_tags:
188                        tagFont = buffer.create_tag(tag_name)
189                        tagFont.set_property('font', family + ' ' + str(size))
190                        self.begin_tags[tag_name] = \
191                                '<span style="font-family: ' + family + '; ' + \
192                                'font-size: ' + str(size) + 'px">'
193                        self.end_tags[tag_name] = '</span>'     
194                        self.fonts_tags.append(tag_name)
195
196                return_val = buffer.get_selection_bounds()
197                if return_val: # if sth was selected
198                        start, finish = return_val[0], return_val[1]
199                else:
200                        start, finish = buffer.get_bounds()
201               
202                for tag in self.fonts_tags:
203                        buffer.remove_tag_by_name(tag, start, finish)
204
205                buffer.apply_tag_by_name(tag_name, start, finish)
206
207                if weight == pango.WEIGHT_BOLD:
208                        buffer.apply_tag_by_name('bold', start, finish)
209                else:
210                        buffer.remove_tag_by_name('bold', start, finish)
211
212                if style == pango.STYLE_ITALIC:
213                        buffer.apply_tag_by_name('italic', start, finish)
214                else:
215                        buffer.remove_tag_by_name('italic', start, finish)
216
217        def get_xhtml(self):
218                buffer = self.get_buffer()
219                old = buffer.get_start_iter()
220                tags = {}
221                tags['bold'] = False
222                iter = buffer.get_start_iter()
223                old = buffer.get_start_iter()
224                texte = ''
225                modified = False
226                def xhtml_special(text):
227                        text = text.replace('<', '&lt;')
228                        text = text.replace('>', '&gt;')
229                        text = text.replace('\n', '<br />')
230                        return text
231
232                for tag in iter.get_toggled_tags(True):
233                        tag_name = tag.get_property('name')
234                        if tag_name not in self.begin_tags:
235                                continue
236                        texte += self.begin_tags[tag_name]
237                        modified = True
238                while (iter.forward_to_tag_toggle(None) and not iter.is_end()):
239                        modified = True
240                        texte += xhtml_special(buffer.get_text(old, iter))
241                        old.forward_to_tag_toggle(None)
242                        new_tags = []
243                        old_tags = []
244                        end_tags = []
245                        for tag in iter.get_toggled_tags(True):
246                                tag_name = tag.get_property('name')
247                                if tag_name not in self.begin_tags:
248                                        continue
249                                new_tags.append(tag_name)
250                       
251                        for tag in iter.get_tags():
252                                tag_name = tag.get_property('name')
253                                if tag_name not in self.begin_tags or tag_name not in self.end_tags:
254                                        continue
255                                if tag_name not in new_tags:
256                                        old_tags.append(tag_name)
257                       
258                        for tag in iter.get_toggled_tags(False):
259                                tag_name = tag.get_property('name')
260                                if tag_name not in self.end_tags:
261                                        continue
262                                end_tags.append(tag_name)
263
264                        for tag in old_tags:
265                                texte += self.end_tags[tag]
266                        for tag in end_tags:
267                                texte += self.end_tags[tag]
268                        for tag in new_tags:
269                                texte += self.begin_tags[tag]
270                        for tag in old_tags:
271                                texte += self.begin_tags[tag]           
272
273                texte += xhtml_special(buffer.get_text(old, buffer.get_end_iter()))
274                for tag in iter.get_toggled_tags(False):
275                        tag_name = tag.get_property('name')
276                        if tag_name not in self.end_tags:
277                                continue
278                        texte += self.end_tags[tag_name]
279
280                if modified:
281                        return '<p>' + self.make_clickable_urls(texte) + '</p>'
282                else:
283                        return None
284       
285
286        def destroy(self):
287                import gc
288                gobject.idle_add(lambda:gc.collect())
289
290        def clear(self, widget = None):
291                '''clear text in the textview'''
292                buffer = self.get_buffer()
293                start, end = buffer.get_bounds()
294                buffer.delete(start, end)
295
296
297# We register depending on keysym and modifier some bindings
298# but we also pass those as param so we can construct fake Event
299# Here we register bindings for those combinations that there is NO DEFAULT
300# action to be done by gtk TextView. In such case we should not add a binding
301# as the default action comes first and our bindings is useless. In that case
302# we catch and do stuff before default action in normal key_press_event
303# and we also return True there to stop the default action from running
304
305# CTRL + SHIFT + TAB
306gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.ISO_Left_Tab,
307        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.ISO_Left_Tab,
308        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
309
310# CTRL + TAB
311gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, 
312        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Tab,
313        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
314       
315# TAB
316gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab, 
317        0, 'mykeypress', int, gtk.keysyms.Tab,  gtk.gdk.ModifierType, 0)
318
319# CTRL + UP
320gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Up, 
321        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Up,
322        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
323
324# CTRL + DOWN
325gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Down, 
326        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Down,
327        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
328
329# ENTER
330gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, 
331        0, 'mykeypress', int, gtk.keysyms.Return,
332        gtk.gdk.ModifierType, 0)
333
334# Ctrl + Enter
335gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return, 
336        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.Return,
337        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
338
339# Keypad Enter
340gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, 
341        0, 'mykeypress', int, gtk.keysyms.KP_Enter,
342        gtk.gdk.ModifierType, 0)
343
344# Ctrl + Keypad Enter
345gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.KP_Enter, 
346        gtk.gdk.CONTROL_MASK, 'mykeypress', int, gtk.keysyms.KP_Enter,
347        gtk.gdk.ModifierType, gtk.gdk.CONTROL_MASK)
348
349# vim: se ts=3:
Note: See TracBrowser for help on using the browser.