[David Flatz] show transports even if show transports is false when they have unread...
[gajim.git] / src / message_textview.py
blob40ba810679fbbceebbd2a3e9c0e28ad842cea49f
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 ## Copyright (C) 2008-2009 Julien Pivotto <roidelapluie AT gmail.com>
8 ##
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/>.
24 import gtk
25 import gobject
26 import pango
27 import gtkgui_helpers
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'''
33 __gsignals__ = dict(
34 mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION,
35 None, # return value
36 (int, gtk.gdk.ModifierType ) # arguments
40 def __init__(self):
41 gtk.TextView.__init__(self)
43 # set properties
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()
56 self.begin_tags = {}
57 self.end_tags = {}
58 self.color_tags = []
59 self.fonts_tags = []
60 self.other_tags = {}
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()
81 start = 0
82 end = 0
83 index = 0
85 new_text = ''
86 iterator = gajim.interface.link_pattern_re.finditer(text)
87 for match in iterator:
88 start, end = match.span()
89 url = text[start:end]
90 if start != 0:
91 text_before_special_text = text[index:start]
92 else:
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
101 if end < len(text):
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()
109 active_tags = []
110 for tag in start.get_tags():
111 active_tags.append(tag.get_property('name'))
112 return active_tags
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]
119 else:
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)
128 else:
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):
141 if response == -6:
142 widget.destroy()
143 return
144 buffer = self.get_buffer()
145 color = color.get_current_color()
146 widget.destroy()
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):
164 if response == -6:
165 widget.destroy()
166 return
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()
178 widget.destroy()
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)
199 else:
200 buffer.remove_tag_by_name('bold', start, finish)
202 if style == pango.STYLE_ITALIC:
203 buffer.apply_tag_by_name('italic', start, finish)
204 else:
205 buffer.remove_tag_by_name('italic', start, finish)
207 def get_xhtml(self):
208 buffer = self.get_buffer()
209 old = buffer.get_start_iter()
210 tags = {}
211 tags['bold'] = False
212 iter = buffer.get_start_iter()
213 old = buffer.get_start_iter()
214 text = ''
215 modified = False
217 def xhtml_special(text):
218 text = text.replace('<', '&lt;')
219 text = text.replace('>', '&gt;')
220 text = text.replace('&', '&amp;')
221 text = text.replace('\n', '<br />')
222 return text
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:
227 continue
228 text += self.begin_tags[tag_name]
229 modified = True
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:
237 continue
238 new_tags.append(tag_name)
239 modified = True
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:
244 continue
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:
251 continue
252 end_tags.append(tag_name)
254 for tag in old_tags:
255 text += self.end_tags[tag]
256 for tag in end_tags:
257 text += self.end_tags[tag]
258 for tag in new_tags:
259 text += self.begin_tags[tag]
260 for tag in old_tags:
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:
267 continue
268 text += self.end_tags[tag_name]
270 if modified:
271 return '<p>' + self.make_clickable_urls(text) + '</p>'
272 else:
273 return None
276 def destroy(self):
277 import gc
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
295 # CTRL + SHIFT + TAB
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)
300 # CTRL + TAB
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)
305 # TAB
306 gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Tab,
307 0, 'mykeypress', int, gtk.keysyms.Tab, gtk.gdk.ModifierType, 0)
309 # CTRL + UP
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)
314 # CTRL + DOWN
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)
319 # ENTER
320 gtk.binding_entry_add_signal(MessageTextView, gtk.keysyms.Return,
321 0, 'mykeypress', int, gtk.keysyms.Return,
322 gtk.gdk.ModifierType, 0)
324 # Ctrl + Enter
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)
329 # Keypad Enter
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)
339 # vim: se ts=3: