2 ## src/message_textview.py
4 ## Copyright (C) 2003-2010 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 ## Copyright (C) 2008-2009 Julien Pivotto <roidelapluie AT gmail.com>
9 ## This file is part of Gajim.
11 ## Gajim is free software; you can redistribute it and/or modify
12 ## it under the terms of the GNU General Public License as published
13 ## by the Free Software Foundation; version 3 only.
15 ## Gajim is distributed in the hope that it will be useful,
16 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ## GNU General Public License for more details.
20 ## You should have received a copy of the GNU General Public License
21 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
31 from common
import gajim
33 class MessageTextView(gtk
.TextView
):
35 Class for the message textview (where user writes new messages) for
36 chat/groupchat windows
40 mykeypress
= (gobject
.SIGNAL_RUN_LAST | gobject
.SIGNAL_ACTION
,
42 (int, gtk
.gdk
.ModifierType
) # arguments
47 gtk
.TextView
.__init
__(self
)
50 self
.set_border_width(1)
51 self
.set_accepts_tab(True)
52 self
.set_editable(True)
53 self
.set_cursor_visible(True)
54 self
.set_wrap_mode(gtk
.WRAP_WORD_CHAR
)
55 self
.set_left_margin(2)
56 self
.set_right_margin(2)
57 self
.set_pixels_above_lines(2)
58 self
.set_pixels_below_lines(2)
62 # needed to know if we undid something
63 self
.undo_pressed
= False
64 self
.lang
= None # Lang used for spell checking
65 _buffer
= self
.get_buffer()
71 self
.other_tags
['bold'] = _buffer
.create_tag('bold')
72 self
.other_tags
['bold'].set_property('weight', pango
.WEIGHT_BOLD
)
73 self
.begin_tags
['bold'] = '<strong>'
74 self
.end_tags
['bold'] = '</strong>'
75 self
.other_tags
['italic'] = _buffer
.create_tag('italic')
76 self
.other_tags
['italic'].set_property('style', pango
.STYLE_ITALIC
)
77 self
.begin_tags
['italic'] = '<em>'
78 self
.end_tags
['italic'] = '</em>'
79 self
.other_tags
['underline'] = _buffer
.create_tag('underline')
80 self
.other_tags
['underline'].set_property('underline', pango
.UNDERLINE_SINGLE
)
81 self
.begin_tags
['underline'] = '<span style="text-decoration: underline;">'
82 self
.end_tags
['underline'] = '</span>'
83 self
.other_tags
['strike'] = _buffer
.create_tag('strike')
84 self
.other_tags
['strike'].set_property('strikethrough', True)
85 self
.begin_tags
['strike'] = '<span style="text-decoration: line-through;">'
86 self
.end_tags
['strike'] = '</span>'
88 def make_clickable_urls(self
, text
):
89 _buffer
= self
.get_buffer()
96 iterator
= gajim
.interface
.link_pattern_re
.finditer(text
)
97 for match
in iterator
:
98 start
, end
= match
.span()
101 text_before_special_text
= text
[index
:start
]
103 text_before_special_text
= ''
104 # we insert normal text
105 new_text
+= text_before_special_text
+ \
106 '<a href="'+ url
+'">' + url
+ '</a>'
108 index
= end
# update index
111 new_text
+= text
[end
:]
113 return new_text
# the position after *last* special text
115 def get_active_tags(self
):
116 start
, finish
= self
.get_active_iters()
118 for tag
in start
.get_tags():
119 active_tags
.append(tag
.get_property('name'))
122 def get_active_iters(self
):
123 _buffer
= self
.get_buffer()
124 return_val
= _buffer
.get_selection_bounds()
125 if return_val
: # if sth was selected
126 start
, finish
= return_val
[0], return_val
[1]
128 start
, finish
= _buffer
.get_bounds()
129 return (start
, finish
)
131 def set_tag(self
, widget
, tag
):
132 _buffer
= self
.get_buffer()
133 start
, finish
= self
.get_active_iters()
134 if start
.has_tag(self
.other_tags
[tag
]):
135 _buffer
.remove_tag_by_name(tag
, start
, finish
)
137 if tag
== 'underline':
138 _buffer
.remove_tag_by_name('strike', start
, finish
)
139 elif tag
== 'strike':
140 _buffer
.remove_tag_by_name('underline', start
, finish
)
141 _buffer
.apply_tag_by_name(tag
, start
, finish
)
143 def clear_tags(self
, widget
):
144 _buffer
= self
.get_buffer()
145 start
, finish
= self
.get_active_iters()
146 _buffer
.remove_all_tags(start
, finish
)
148 def color_set(self
, widget
, response
, color
):
152 _buffer
= self
.get_buffer()
153 color
= color
.get_current_color()
155 color_string
= gtkgui_helpers
.make_color_string(color
)
156 tag_name
= 'color' + color_string
157 if not tag_name
in self
.color_tags
:
158 tagColor
= _buffer
.create_tag(tag_name
)
159 tagColor
.set_property('foreground', color_string
)
160 self
.begin_tags
[tag_name
] = '<span style="color: ' + color_string
+ ';">'
161 self
.end_tags
[tag_name
] = '</span>'
162 self
.color_tags
.append(tag_name
)
164 start
, finish
= self
.get_active_iters()
166 for tag
in self
.color_tags
:
167 _buffer
.remove_tag_by_name(tag
, start
, finish
)
169 _buffer
.apply_tag_by_name(tag_name
, start
, finish
)
171 def font_set(self
, widget
, response
, font
):
176 _buffer
= self
.get_buffer()
178 font
= font
.get_font_name()
179 font_desc
= pango
.FontDescription(font
)
180 family
= font_desc
.get_family()
181 size
= font_desc
.get_size()
182 size
= size
/ pango
.SCALE
183 weight
= font_desc
.get_weight()
184 style
= font_desc
.get_style()
188 tag_name
= 'font' + font
189 if not tag_name
in self
.fonts_tags
:
190 tagFont
= _buffer
.create_tag(tag_name
)
191 tagFont
.set_property('font', family
+ ' ' + str(size
))
192 self
.begin_tags
[tag_name
] = \
193 '<span style="font-family: ' + family
+ '; ' + \
194 'font-size: ' + str(size
) + 'px">'
195 self
.end_tags
[tag_name
] = '</span>'
196 self
.fonts_tags
.append(tag_name
)
198 start
, finish
= self
.get_active_iters()
200 for tag
in self
.fonts_tags
:
201 _buffer
.remove_tag_by_name(tag
, start
, finish
)
203 _buffer
.apply_tag_by_name(tag_name
, start
, finish
)
205 if weight
== pango
.WEIGHT_BOLD
:
206 _buffer
.apply_tag_by_name('bold', start
, finish
)
208 _buffer
.remove_tag_by_name('bold', start
, finish
)
210 if style
== pango
.STYLE_ITALIC
:
211 _buffer
.apply_tag_by_name('italic', start
, finish
)
213 _buffer
.remove_tag_by_name('italic', start
, finish
)
216 _buffer
= self
.get_buffer()
217 old
= _buffer
.get_start_iter()
220 iter = _buffer
.get_start_iter()
221 old
= _buffer
.get_start_iter()
225 def xhtml_special(text
):
226 text
= text
.replace('<', '<')
227 text
= text
.replace('>', '>')
228 text
= text
.replace('&', '&')
229 text
= text
.replace('\n', '<br />')
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
:
236 text
+= self
.begin_tags
[tag_name
]
238 while (iter.forward_to_tag_toggle(None) and not iter.is_end()):
239 text
+= xhtml_special(_buffer
.get_text(old
, iter))
240 old
.forward_to_tag_toggle(None)
241 new_tags
, old_tags
, end_tags
= [], [], []
242 for tag
in iter.get_toggled_tags(True):
243 tag_name
= tag
.get_property('name')
244 if tag_name
not in self
.begin_tags
:
246 new_tags
.append(tag_name
)
249 for tag
in iter.get_tags():
250 tag_name
= tag
.get_property('name')
251 if tag_name
not in self
.begin_tags
or tag_name
not in self
.end_tags
:
253 if tag_name
not in new_tags
:
254 old_tags
.append(tag_name
)
256 for tag
in iter.get_toggled_tags(False):
257 tag_name
= tag
.get_property('name')
258 if tag_name
not in self
.end_tags
:
260 end_tags
.append(tag_name
)
263 text
+= self
.end_tags
[tag
]
265 text
+= self
.end_tags
[tag
]
267 text
+= self
.begin_tags
[tag
]
269 text
+= self
.begin_tags
[tag
]
271 text
+= xhtml_special(_buffer
.get_text(old
, _buffer
.get_end_iter()))
272 for tag
in iter.get_toggled_tags(False):
273 tag_name
= tag
.get_property('name')
274 if tag_name
not in self
.end_tags
:
276 text
+= self
.end_tags
[tag_name
]
279 return '<p>' + self
.make_clickable_urls(text
) + '</p>'
284 gobject
.idle_add(gc
.collect
)
286 def clear(self
, widget
= None):
288 Clear text in the textview
290 _buffer
= self
.get_buffer()
291 start
, end
= _buffer
.get_bounds()
292 _buffer
.delete(start
, end
)
294 def save_undo(self
, text
):
295 self
.undo_list
.append(text
)
296 if len(self
.undo_list
) > self
.UNDO_LIMIT
:
297 del self
.undo_list
[0]
298 self
.undo_pressed
= False
300 def undo(self
, widget
=None):
302 Undo text in the textview
304 _buffer
= self
.get_buffer()
306 _buffer
.set_text(self
.undo_list
.pop())
307 self
.undo_pressed
= True
309 def get_sensitive(self
):
310 # get sensitive is not in GTK < 2.18
312 return super(MessageTextView
, self
).get_sensitive()
313 except AttributeError:
314 return self
.get_property('sensitive')
316 # We register depending on keysym and modifier some bindings
317 # but we also pass those as param so we can construct fake Event
318 # Here we register bindings for those combinations that there is NO DEFAULT
319 # action to be done by gtk TextView. In such case we should not add a binding
320 # as the default action comes first and our bindings is useless. In that case
321 # we catch and do stuff before default action in normal key_press_event
322 # and we also return True there to stop the default action from running
325 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.ISO_Left_Tab
,
326 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.ISO_Left_Tab
,
327 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
330 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Tab
,
331 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Tab
,
332 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
335 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Tab
,
336 0, 'mykeypress', int, gtk
.keysyms
.Tab
, gtk
.gdk
.ModifierType
, 0)
339 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Up
,
340 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Up
,
341 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
344 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Down
,
345 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Down
,
346 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
349 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Up
,
350 gtk
.gdk
.CONTROL_MASK | gtk
.gdk
.SHIFT_MASK
, 'mykeypress', int,
351 gtk
.keysyms
.Up
, gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK |
354 # CTRL + SHIFT + DOWN
355 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Down
,
356 gtk
.gdk
.CONTROL_MASK | gtk
.gdk
.SHIFT_MASK
, 'mykeypress', int,
357 gtk
.keysyms
.Down
, gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK |
361 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Return
,
362 0, 'mykeypress', int, gtk
.keysyms
.Return
,
363 gtk
.gdk
.ModifierType
, 0)
366 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Return
,
367 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Return
,
368 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
371 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.KP_Enter
,
372 0, 'mykeypress', int, gtk
.keysyms
.KP_Enter
,
373 gtk
.gdk
.ModifierType
, 0)
375 # Ctrl + Keypad Enter
376 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.KP_Enter
,
377 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.KP_Enter
,
378 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
381 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.z
,
382 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.z
,
383 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)