2 ## src/message_textview.py
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 ## 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/>.
28 from common
import gajim
30 class MessageTextView(gtk
.TextView
):
31 '''Class for the message textview (where user writes new messages)
32 for chat/groupchat windows'''
34 mykeypress
= (gobject
.SIGNAL_RUN_LAST | gobject
.SIGNAL_ACTION
,
36 (int, gtk
.gdk
.ModifierType
) # arguments
41 gtk
.TextView
.__init
__(self
)
44 self
.set_border_width(1)
45 self
.set_accepts_tab(True)
46 self
.set_editable(True)
47 self
.set_cursor_visible(True)
48 self
.set_wrap_mode(gtk
.WRAP_WORD_CHAR
)
49 self
.set_left_margin(2)
50 self
.set_right_margin(2)
51 self
.set_pixels_above_lines(2)
52 self
.set_pixels_below_lines(2)
54 self
.lang
= None # Lang used for spell checking
55 buffer = self
.get_buffer()
61 self
.other_tags
['bold'] = buffer.create_tag('bold')
62 self
.other_tags
['bold'].set_property('weight', pango
.WEIGHT_BOLD
)
63 self
.begin_tags
['bold'] = '<strong>'
64 self
.end_tags
['bold'] = '</strong>'
65 self
.other_tags
['italic'] = buffer.create_tag('italic')
66 self
.other_tags
['italic'].set_property('style', pango
.STYLE_ITALIC
)
67 self
.begin_tags
['italic'] = '<em>'
68 self
.end_tags
['italic'] = '</em>'
69 self
.other_tags
['underline'] = buffer.create_tag('underline')
70 self
.other_tags
['underline'].set_property('underline', pango
.UNDERLINE_SINGLE
)
71 self
.begin_tags
['underline'] = '<span style="text-decoration: underline;">'
72 self
.end_tags
['underline'] = '</span>'
73 self
.other_tags
['strike'] = buffer.create_tag('strike')
74 self
.other_tags
['strike'].set_property('strikethrough', True)
75 self
.begin_tags
['strike'] = '<span style="text-decoration: line-through;">'
76 self
.end_tags
['strike'] = '</span>'
78 def make_clickable_urls(self
, text
):
79 buffer = self
.get_buffer()
86 iterator
= gajim
.interface
.link_pattern_re
.finditer(text
)
87 for match
in iterator
:
88 start
, end
= match
.span()
91 text_before_special_text
= text
[index
:start
]
93 text_before_special_text
= ''
94 end_iter
= buffer.get_end_iter()
95 # we insert normal text
96 new_text
+= text_before_special_text
+ \
97 '<a href="'+ url
+'">' + url
+ '</a>'
99 index
= end
# update index
102 new_text
+= text
[end
:]
104 return new_text
# the position after *last* special text
106 def get_active_tags(self
):
107 buffer = self
.get_buffer()
108 start
, finish
= self
.get_active_iters()
110 for tag
in start
.get_tags():
111 active_tags
.append(tag
.get_property('name'))
114 def get_active_iters(self
):
115 buffer = self
.get_buffer()
116 return_val
= buffer.get_selection_bounds()
117 if return_val
: # if sth was selected
118 start
, finish
= return_val
[0], return_val
[1]
120 start
, finish
= buffer.get_bounds()
121 return (start
, finish
)
123 def set_tag(self
, widget
, tag
):
124 buffer = self
.get_buffer()
125 start
, finish
= self
.get_active_iters()
126 if start
.has_tag(self
.other_tags
[tag
]):
127 buffer.remove_tag_by_name(tag
, start
, finish
)
129 if tag
== 'underline':
130 buffer.remove_tag_by_name('strike', start
, finish
)
131 elif tag
== 'strike':
132 buffer.remove_tag_by_name('underline', start
, finish
)
133 buffer.apply_tag_by_name(tag
, start
, finish
)
135 def clear_tags(self
, widget
):
136 buffer = self
.get_buffer()
137 start
, finish
= self
.get_active_iters()
138 buffer.remove_all_tags(start
, finish
)
140 def color_set(self
, widget
, response
, color
):
144 buffer = self
.get_buffer()
145 color
= color
.get_current_color()
147 color_string
= gtkgui_helpers
.make_color_string(color
)
148 tag_name
= 'color' + color_string
149 if not tag_name
in self
.color_tags
:
150 tagColor
= buffer.create_tag(tag_name
)
151 tagColor
.set_property('foreground', color_string
)
152 self
.begin_tags
[tag_name
] = '<span style="color: ' + color_string
+ ';">'
153 self
.end_tags
[tag_name
] = '</span>'
154 self
.color_tags
.append(tag_name
)
156 start
, finish
= self
.get_active_iters()
158 for tag
in self
.color_tags
:
159 buffer.remove_tag_by_name(tag
, start
, finish
)
161 buffer.apply_tag_by_name(tag_name
, start
, finish
)
163 def font_set(self
, widget
, response
, font
):
168 buffer = self
.get_buffer()
170 font
= font
.get_font_name()
171 font_desc
= pango
.FontDescription(font
)
172 family
= font_desc
.get_family()
173 size
= font_desc
.get_size()
174 size
= size
/ pango
.SCALE
175 weight
= font_desc
.get_weight()
176 style
= font_desc
.get_style()
180 tag_name
= 'font' + font
181 if not tag_name
in self
.fonts_tags
:
182 tagFont
= buffer.create_tag(tag_name
)
183 tagFont
.set_property('font', family
+ ' ' + str(size
))
184 self
.begin_tags
[tag_name
] = \
185 '<span style="font-family: ' + family
+ '; ' + \
186 'font-size: ' + str(size
) + 'px">'
187 self
.end_tags
[tag_name
] = '</span>'
188 self
.fonts_tags
.append(tag_name
)
190 start
, finish
= self
.get_active_iters()
192 for tag
in self
.fonts_tags
:
193 buffer.remove_tag_by_name(tag
, start
, finish
)
195 buffer.apply_tag_by_name(tag_name
, start
, finish
)
197 if weight
== pango
.WEIGHT_BOLD
:
198 buffer.apply_tag_by_name('bold', start
, finish
)
200 buffer.remove_tag_by_name('bold', start
, finish
)
202 if style
== pango
.STYLE_ITALIC
:
203 buffer.apply_tag_by_name('italic', start
, finish
)
205 buffer.remove_tag_by_name('italic', start
, finish
)
208 buffer = self
.get_buffer()
209 old
= buffer.get_start_iter()
212 iter = buffer.get_start_iter()
213 old
= buffer.get_start_iter()
217 def xhtml_special(text
):
218 text
= text
.replace('<', '<')
219 text
= text
.replace('>', '>')
220 text
= text
.replace('&', '&')
221 text
= text
.replace('\n', '<br />')
224 for tag
in iter.get_toggled_tags(True):
225 tag_name
= tag
.get_property('name')
226 if tag_name
not in self
.begin_tags
:
228 text
+= self
.begin_tags
[tag_name
]
230 while (iter.forward_to_tag_toggle(None) and not iter.is_end()):
231 text
+= xhtml_special(buffer.get_text(old
, iter))
232 old
.forward_to_tag_toggle(None)
233 new_tags
, old_tags
, end_tags
= [], [], []
234 for tag
in iter.get_toggled_tags(True):
235 tag_name
= tag
.get_property('name')
236 if tag_name
not in self
.begin_tags
:
238 new_tags
.append(tag_name
)
241 for tag
in iter.get_tags():
242 tag_name
= tag
.get_property('name')
243 if tag_name
not in self
.begin_tags
or tag_name
not in self
.end_tags
:
245 if tag_name
not in new_tags
:
246 old_tags
.append(tag_name
)
248 for tag
in iter.get_toggled_tags(False):
249 tag_name
= tag
.get_property('name')
250 if tag_name
not in self
.end_tags
:
252 end_tags
.append(tag_name
)
255 text
+= self
.end_tags
[tag
]
257 text
+= self
.end_tags
[tag
]
259 text
+= self
.begin_tags
[tag
]
261 text
+= self
.begin_tags
[tag
]
263 text
+= xhtml_special(buffer.get_text(old
, buffer.get_end_iter()))
264 for tag
in iter.get_toggled_tags(False):
265 tag_name
= tag
.get_property('name')
266 if tag_name
not in self
.end_tags
:
268 text
+= self
.end_tags
[tag_name
]
271 return '<p>' + self
.make_clickable_urls(text
) + '</p>'
278 gobject
.idle_add(lambda:gc
.collect())
280 def clear(self
, widget
= None):
281 '''clear text in the textview'''
282 buffer_
= self
.get_buffer()
283 start
, end
= buffer_
.get_bounds()
284 buffer_
.delete(start
, end
)
287 # We register depending on keysym and modifier some bindings
288 # but we also pass those as param so we can construct fake Event
289 # Here we register bindings for those combinations that there is NO DEFAULT
290 # action to be done by gtk TextView. In such case we should not add a binding
291 # as the default action comes first and our bindings is useless. In that case
292 # we catch and do stuff before default action in normal key_press_event
293 # and we also return True there to stop the default action from running
296 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.ISO_Left_Tab
,
297 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.ISO_Left_Tab
,
298 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
301 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Tab
,
302 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Tab
,
303 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
306 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Tab
,
307 0, 'mykeypress', int, gtk
.keysyms
.Tab
, gtk
.gdk
.ModifierType
, 0)
310 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Up
,
311 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Up
,
312 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
315 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Down
,
316 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Down
,
317 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
320 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Return
,
321 0, 'mykeypress', int, gtk
.keysyms
.Return
,
322 gtk
.gdk
.ModifierType
, 0)
325 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.Return
,
326 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.Return
,
327 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)
330 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.KP_Enter
,
331 0, 'mykeypress', int, gtk
.keysyms
.KP_Enter
,
332 gtk
.gdk
.ModifierType
, 0)
334 # Ctrl + Keypad Enter
335 gtk
.binding_entry_add_signal(MessageTextView
, gtk
.keysyms
.KP_Enter
,
336 gtk
.gdk
.CONTROL_MASK
, 'mykeypress', int, gtk
.keysyms
.KP_Enter
,
337 gtk
.gdk
.ModifierType
, gtk
.gdk
.CONTROL_MASK
)