| ... |
... |
@@ -56,34 +56,32 @@ from gajim.gui_menu_builder import get_conv_context_menu |
|
56
|
56
|
|
|
57
|
57
|
log = logging.getLogger('gajim.htmlview')
|
|
58
|
58
|
|
|
59
|
|
-__all__ = ['HtmlTextView']
|
|
60
|
|
-
|
|
61
|
59
|
whitespace_rx = re.compile('\\s+')
|
|
62
|
60
|
allwhitespace_rx = re.compile('^\\s*$')
|
|
63
|
61
|
|
|
64
|
62
|
# embryo of CSS classes
|
|
65
|
63
|
classes = {
|
|
66
|
|
- #'system-message':';display: none',
|
|
67
|
|
- 'problematic': ';color: red',
|
|
|
64
|
+ #'system-message':';display: none',
|
|
|
65
|
+ 'problematic': ';color: red',
|
|
68
|
66
|
}
|
|
69
|
67
|
|
|
70
|
68
|
# styles for elements
|
|
71
|
69
|
_element_styles = {
|
|
72
|
|
- 'u' : ';text-decoration: underline',
|
|
73
|
|
- 'em' : ';font-style: oblique',
|
|
74
|
|
- 'cite' : '; background-color:rgb(170,190,250);'
|
|
75
|
|
- 'font-style: oblique',
|
|
76
|
|
- 'li' : '; margin-left: 1em; margin-right: 10%',
|
|
77
|
|
- 'strong' : ';font-weight: bold',
|
|
78
|
|
- 'pre' : '; background-color:rgb(190,190,190);'
|
|
79
|
|
- 'font-family: monospace; white-space: pre;'
|
|
80
|
|
- 'margin-left: 1em; margin-right: 10%',
|
|
81
|
|
- 'kbd' : ';background-color:rgb(210,210,210);'
|
|
82
|
|
- 'font-family: monospace',
|
|
83
|
|
- 'blockquote' : '; background-color:rgb(170,190,250);'
|
|
84
|
|
- 'margin-left: 2em; margin-right: 10%',
|
|
85
|
|
- 'dt' : ';font-weight: bold; font-style: oblique',
|
|
86
|
|
- 'dd' : ';margin-left: 2em; font-style: oblique'
|
|
|
70
|
+ 'u' : ';text-decoration: underline',
|
|
|
71
|
+ 'em' : ';font-style: oblique',
|
|
|
72
|
+ 'cite' : '; background-color:rgb(170,190,250);'
|
|
|
73
|
+ 'font-style: oblique',
|
|
|
74
|
+ 'li' : '; margin-left: 1em; margin-right: 10%',
|
|
|
75
|
+ 'strong' : ';font-weight: bold',
|
|
|
76
|
+ 'pre' : '; background-color:rgb(190,190,190);'
|
|
|
77
|
+ 'font-family: monospace; white-space: pre;'
|
|
|
78
|
+ 'margin-left: 1em; margin-right: 10%',
|
|
|
79
|
+ 'kbd' : ';background-color:rgb(210,210,210);'
|
|
|
80
|
+ 'font-family: monospace',
|
|
|
81
|
+ 'blockquote' : '; background-color:rgb(170,190,250);'
|
|
|
82
|
+ 'margin-left: 2em; margin-right: 10%',
|
|
|
83
|
+ 'dt' : ';font-weight: bold; font-style: oblique',
|
|
|
84
|
+ 'dd' : ';margin-left: 2em; font-style: oblique'
|
|
87
|
85
|
}
|
|
88
|
86
|
# no difference for the moment
|
|
89
|
87
|
_element_styles['dfn'] = _element_styles['em']
|
| ... |
... |
@@ -93,6 +91,12 @@ _element_styles['tt'] = _element_styles['kbd'] |
|
93
|
91
|
_element_styles['i'] = _element_styles['em']
|
|
94
|
92
|
_element_styles['b'] = _element_styles['strong']
|
|
95
|
93
|
|
|
|
94
|
+_supported_style_attrs = [
|
|
|
95
|
+ 'background-color', 'color', 'font-family', 'font-size', 'font-style',
|
|
|
96
|
+ 'font-weight', 'margin-left', 'margin-right', 'text-align',
|
|
|
97
|
+ 'text-decoration', 'white-space', 'display', 'width', 'height'
|
|
|
98
|
+]
|
|
|
99
|
+
|
|
96
|
100
|
# ==========
|
|
97
|
101
|
# XEP-0071
|
|
98
|
102
|
# ==========
|
| ... |
... |
@@ -182,8 +186,9 @@ for _name in BLOCK_HEAD: |
|
182
|
186
|
_num = int(_name[1])
|
|
183
|
187
|
_header_size = (_num - 1) // 2
|
|
184
|
188
|
_weight = (_num - 1) % 2
|
|
185
|
|
- _element_styles[_name] = '; font-size: %s; %s' % (('large', 'medium', 'small')[_header_size],
|
|
186
|
|
- ('font-weight: bold', 'font-style: oblique')[_weight],)
|
|
|
189
|
+ _element_styles[_name] = '; font-size: %s; %s' % (
|
|
|
190
|
+ ('large', 'medium', 'small')[_header_size],
|
|
|
191
|
+ ('font-weight: bold', 'font-style: oblique')[_weight])
|
|
187
|
192
|
|
|
188
|
193
|
def _parse_css_color(color):
|
|
189
|
194
|
if color.startswith('rgb(') and color.endswith(')'):
|
| ... |
... |
@@ -216,26 +221,35 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
216
|
221
|
self.list_counters = [] # stack (top at head) of list
|
|
217
|
222
|
# counters, or None for unordered list
|
|
218
|
223
|
|
|
|
224
|
+ # build a dictionary mapping styles to methods
|
|
|
225
|
+ self.__style_methods = {}
|
|
|
226
|
+ for style in _supported_style_attrs:
|
|
|
227
|
+ method_names = '_parse_style_%s' % style.replace('-', '_')
|
|
|
228
|
+ self.__style_methods[style] = method_names
|
|
|
229
|
+
|
|
219
|
230
|
def _get_points_from_pixels(self, pixels):
|
|
220
|
231
|
resolution = self.textview.get_screen().get_resolution()
|
|
221
|
232
|
# points = pixels * 72 / resolution
|
|
222
|
233
|
return pixels * 72 / resolution
|
|
223
|
234
|
|
|
224
|
|
- def _parse_style_color(self, tag, value):
|
|
|
235
|
+ @staticmethod
|
|
|
236
|
+ def _parse_style_color(tag, value):
|
|
225
|
237
|
color = _parse_css_color(value)
|
|
226
|
238
|
tag.set_property('foreground-gdk', color)
|
|
227
|
239
|
|
|
228
|
|
- def _parse_style_background_color(self, tag, value):
|
|
|
240
|
+ @staticmethod
|
|
|
241
|
+ def _parse_style_background_color(tag, value):
|
|
229
|
242
|
color = _parse_css_color(value)
|
|
230
|
243
|
tag.set_property('background-gdk', color)
|
|
231
|
244
|
tag.set_property('paragraph-background-gdk', color)
|
|
232
|
245
|
|
|
233
|
|
- def __parse_length_frac_size_allocate(self, textview, allocation, frac,
|
|
234
|
|
- callback, args):
|
|
|
246
|
+ @staticmethod
|
|
|
247
|
+ def __parse_length_frac_size_allocate(_textview, allocation, frac,
|
|
|
248
|
+ callback, args):
|
|
235
|
249
|
callback(allocation.width*frac, *args)
|
|
236
|
250
|
|
|
237
|
251
|
def _parse_length(self, value, font_relative, block_relative, minl, maxl,
|
|
238
|
|
- callback, *args):
|
|
|
252
|
+ callback, *args):
|
|
239
|
253
|
"""
|
|
240
|
254
|
Parse/calc length, converting to pixels, calls callback(length, *args)
|
|
241
|
255
|
when the length is first computed or changes
|
| ... |
... |
@@ -259,11 +273,11 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
259
|
273
|
# This is difficult/impossible to implement, so we use
|
|
260
|
274
|
# textview width instead; a reasonable approximation..
|
|
261
|
275
|
alloc = self.textview.get_allocation()
|
|
262
|
|
- self.__parse_length_frac_size_allocate(self.textview, alloc,
|
|
263
|
|
- frac, callback, args)
|
|
|
276
|
+ self.__parse_length_frac_size_allocate(
|
|
|
277
|
+ self.textview, alloc, frac, callback, args)
|
|
264
|
278
|
self.textview.connect('size-allocate',
|
|
265
|
|
- self.__parse_length_frac_size_allocate,
|
|
266
|
|
- frac, callback, args)
|
|
|
279
|
+ self.__parse_length_frac_size_allocate,
|
|
|
280
|
+ frac, callback, args)
|
|
267
|
281
|
else:
|
|
268
|
282
|
callback(frac, *args)
|
|
269
|
283
|
return
|
| ... |
... |
@@ -324,7 +338,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
324
|
338
|
elif type_ == 'px':
|
|
325
|
339
|
tag.set_property('size-points', self._get_points_from_pixels(size))
|
|
326
|
340
|
|
|
327
|
|
- def _parse_style_display(self, tag, value):
|
|
|
341
|
+ @staticmethod
|
|
|
342
|
+ def _parse_style_display(tag, value):
|
|
328
|
343
|
if value == 'none':
|
|
329
|
344
|
tag.set_property('invisible', 'true')
|
|
330
|
345
|
# FIXME: display: block, inline
|
| ... |
... |
@@ -355,13 +370,14 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
355
|
370
|
self._parse_length(
|
|
356
|
371
|
value, True, False, 5, 110, self.__parse_font_size_cb, tag)
|
|
357
|
372
|
|
|
358
|
|
- def _parse_style_font_style(self, tag, value):
|
|
|
373
|
+ @staticmethod
|
|
|
374
|
+ def _parse_style_font_style(tag, value):
|
|
359
|
375
|
try:
|
|
360
|
376
|
style = {
|
|
361
|
|
- 'normal': Pango.Style.NORMAL,
|
|
362
|
|
- 'italic': Pango.Style.ITALIC,
|
|
363
|
|
- 'oblique': Pango.Style.OBLIQUE,
|
|
364
|
|
- }[value]
|
|
|
377
|
+ 'normal': Pango.Style.NORMAL,
|
|
|
378
|
+ 'italic': Pango.Style.ITALIC,
|
|
|
379
|
+ 'oblique': Pango.Style.OBLIQUE,
|
|
|
380
|
+ }[value]
|
|
365
|
381
|
except KeyError:
|
|
366
|
382
|
log.warning('unknown font-style %s', value)
|
|
367
|
383
|
else:
|
| ... |
... |
@@ -376,51 +392,55 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
376
|
392
|
def _parse_style_margin_left(self, tag, value):
|
|
377
|
393
|
# block relative
|
|
378
|
394
|
self._parse_length(value, False, True, 1, 1000,
|
|
379
|
|
- self.__frac_length_tag_cb, tag, 'left-margin')
|
|
|
395
|
+ self.__frac_length_tag_cb, tag, 'left-margin')
|
|
380
|
396
|
|
|
381
|
397
|
def _parse_style_margin_right(self, tag, value):
|
|
382
|
398
|
# block relative
|
|
383
|
399
|
self._parse_length(value, False, True, 1, 1000,
|
|
384
|
|
- self.__frac_length_tag_cb, tag, 'right-margin')
|
|
|
400
|
+ self.__frac_length_tag_cb, tag, 'right-margin')
|
|
385
|
401
|
|
|
386
|
|
- def _parse_style_font_weight(self, tag, value):
|
|
|
402
|
+ @staticmethod
|
|
|
403
|
+ def _parse_style_font_weight(tag, value):
|
|
387
|
404
|
# TODO: missing 'bolder' and 'lighter'
|
|
388
|
405
|
try:
|
|
389
|
406
|
weight = {
|
|
390
|
|
- '100': Pango.Weight.ULTRALIGHT,
|
|
391
|
|
- '200': Pango.Weight.ULTRALIGHT,
|
|
392
|
|
- '300': Pango.Weight.LIGHT,
|
|
393
|
|
- '400': Pango.Weight.NORMAL,
|
|
394
|
|
- '500': Pango.Weight.NORMAL,
|
|
395
|
|
- '600': Pango.Weight.BOLD,
|
|
396
|
|
- '700': Pango.Weight.BOLD,
|
|
397
|
|
- '800': Pango.Weight.ULTRABOLD,
|
|
398
|
|
- '900': Pango.Weight.HEAVY,
|
|
399
|
|
- 'normal': Pango.Weight.NORMAL,
|
|
400
|
|
- 'bold': Pango.Weight.BOLD,
|
|
401
|
|
- }[value]
|
|
|
407
|
+ '100': Pango.Weight.ULTRALIGHT,
|
|
|
408
|
+ '200': Pango.Weight.ULTRALIGHT,
|
|
|
409
|
+ '300': Pango.Weight.LIGHT,
|
|
|
410
|
+ '400': Pango.Weight.NORMAL,
|
|
|
411
|
+ '500': Pango.Weight.NORMAL,
|
|
|
412
|
+ '600': Pango.Weight.BOLD,
|
|
|
413
|
+ '700': Pango.Weight.BOLD,
|
|
|
414
|
+ '800': Pango.Weight.ULTRABOLD,
|
|
|
415
|
+ '900': Pango.Weight.HEAVY,
|
|
|
416
|
+ 'normal': Pango.Weight.NORMAL,
|
|
|
417
|
+ 'bold': Pango.Weight.BOLD,
|
|
|
418
|
+ }[value]
|
|
402
|
419
|
except KeyError:
|
|
403
|
420
|
log.warning('unknown font-style %s', value)
|
|
404
|
421
|
else:
|
|
405
|
422
|
tag.set_property('weight', weight)
|
|
406
|
423
|
|
|
407
|
|
- def _parse_style_font_family(self, tag, value):
|
|
|
424
|
+ @staticmethod
|
|
|
425
|
+ def _parse_style_font_family(tag, value):
|
|
408
|
426
|
tag.set_property('family', value)
|
|
409
|
427
|
|
|
410
|
|
- def _parse_style_text_align(self, tag, value):
|
|
|
428
|
+ @staticmethod
|
|
|
429
|
+ def _parse_style_text_align(tag, value):
|
|
411
|
430
|
try:
|
|
412
|
431
|
align = {
|
|
413
|
|
- 'left': Gtk.Justification.LEFT,
|
|
414
|
|
- 'right': Gtk.Justification.RIGHT,
|
|
415
|
|
- 'center': Gtk.Justification.CENTER,
|
|
416
|
|
- 'justify': Gtk.Justification.FILL,
|
|
417
|
|
- }[value]
|
|
|
432
|
+ 'left': Gtk.Justification.LEFT,
|
|
|
433
|
+ 'right': Gtk.Justification.RIGHT,
|
|
|
434
|
+ 'center': Gtk.Justification.CENTER,
|
|
|
435
|
+ 'justify': Gtk.Justification.FILL,
|
|
|
436
|
+ }[value]
|
|
418
|
437
|
except KeyError:
|
|
419
|
438
|
log.warning('Invalid text-align: %s requested', value)
|
|
420
|
439
|
else:
|
|
421
|
440
|
tag.set_property('justification', align)
|
|
422
|
441
|
|
|
423
|
|
- def _parse_style_text_decoration(self, tag, value):
|
|
|
442
|
+ @staticmethod
|
|
|
443
|
+ def _parse_style_text_decoration(tag, value):
|
|
424
|
444
|
values = value.split(' ')
|
|
425
|
445
|
if 'none' in values:
|
|
426
|
446
|
tag.set_property('underline', Pango.Underline.NONE)
|
| ... |
... |
@@ -438,7 +458,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
438
|
458
|
if 'overline' in values:
|
|
439
|
459
|
log.warning('text-decoration:overline not implemented')
|
|
440
|
460
|
|
|
441
|
|
- def _parse_style_white_space(self, tag, value):
|
|
|
461
|
+ @staticmethod
|
|
|
462
|
+ def _parse_style_white_space(tag, value):
|
|
442
|
463
|
if value == 'pre':
|
|
443
|
464
|
tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
|
|
444
|
465
|
elif value == 'normal':
|
| ... |
... |
@@ -446,7 +467,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
446
|
467
|
elif value == 'nowrap':
|
|
447
|
468
|
tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
|
|
448
|
469
|
|
|
449
|
|
- def __length_tag_cb(self, value, tag, propname):
|
|
|
470
|
+ @staticmethod
|
|
|
471
|
+ def __length_tag_cb(value, tag, propname):
|
|
450
|
472
|
try:
|
|
451
|
473
|
tag.set_property(propname, value)
|
|
452
|
474
|
except Exception:
|
| ... |
... |
@@ -455,29 +477,13 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
455
|
477
|
def _parse_style_width(self, tag, value):
|
|
456
|
478
|
if value == 'auto':
|
|
457
|
479
|
return
|
|
458
|
|
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
|
|
459
|
|
- tag, "width")
|
|
|
480
|
+ self._parse_length(value, False, False, 1, 1000,
|
|
|
481
|
+ self.__length_tag_cb, tag, "width")
|
|
460
|
482
|
def _parse_style_height(self, tag, value):
|
|
461
|
483
|
if value == 'auto':
|
|
462
|
484
|
return
|
|
463
|
|
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
|
|
464
|
|
- tag, "height")
|
|
465
|
|
-
|
|
466
|
|
-
|
|
467
|
|
- # build a dictionary mapping styles to methods, for greater speed
|
|
468
|
|
- __style_methods = dict()
|
|
469
|
|
- for style in ('background-color', 'color', 'font-family', 'font-size',
|
|
470
|
|
- 'font-style', 'font-weight', 'margin-left', 'margin-right',
|
|
471
|
|
- 'text-align', 'text-decoration', 'white-space', 'display',
|
|
472
|
|
- 'width', 'height'):
|
|
473
|
|
- try:
|
|
474
|
|
- method = locals()['_parse_style_%s' % style.replace('-', '_')]
|
|
475
|
|
- except KeyError:
|
|
476
|
|
- log.warning('Style attribute "%s" not yet implemented', style)
|
|
477
|
|
- else:
|
|
478
|
|
- __style_methods[style] = method
|
|
479
|
|
- del style # pylint: disable=undefined-loop-variable
|
|
480
|
|
- # --
|
|
|
485
|
+ self._parse_length(value, False, False, 1, 1000,
|
|
|
486
|
+ self.__length_tag_cb, tag, "height")
|
|
481
|
487
|
|
|
482
|
488
|
def _get_style_tags(self):
|
|
483
|
489
|
return [tag for tag in self.styles if tag is not None]
|
| ... |
... |
@@ -523,8 +529,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
523
|
529
|
else:
|
|
524
|
530
|
if self.conv_textview:
|
|
525
|
531
|
img_mark = self.textbuf.create_mark(None, self.iter, True)
|
|
526
|
|
- app.thread_interface(helpers.download_image, [
|
|
527
|
|
- self.conv_textview.account, attrs], self._update_img,
|
|
|
532
|
+ app.thread_interface(
|
|
|
533
|
+ helpers.download_image,
|
|
|
534
|
+ [self.conv_textview.account, attrs],
|
|
|
535
|
+ self._update_img,
|
|
528
|
536
|
[attrs, img_mark, self._get_style_tags()])
|
|
529
|
537
|
alt = attrs.get('alt', '')
|
|
530
|
538
|
if alt:
|
| ... |
... |
@@ -542,34 +550,36 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
542
|
550
|
def width_cb(length):
|
|
543
|
551
|
dims[0] = length
|
|
544
|
552
|
# process width and height attributes
|
|
545
|
|
- w = attrs.get('width')
|
|
546
|
|
- h = attrs.get('height')
|
|
|
553
|
+ width = attrs.get('width')
|
|
|
554
|
+ height = attrs.get('height')
|
|
547
|
555
|
# override with width and height styles
|
|
548
|
556
|
for attr, val in style_iter(attrs.get('style', '')):
|
|
549
|
557
|
if attr == 'width':
|
|
550
|
|
- w = val
|
|
|
558
|
+ width = val
|
|
551
|
559
|
elif attr == 'height':
|
|
552
|
|
- h = val
|
|
553
|
|
- if w:
|
|
554
|
|
- self._parse_length(w, False, False, 1, 1000, width_cb)
|
|
555
|
|
- if h:
|
|
556
|
|
- self._parse_length(h, False, False, 1, 1000, height_cb)
|
|
557
|
|
- def set_size(pixbuf, w, h, dims):
|
|
|
560
|
+ height = val
|
|
|
561
|
+ if width:
|
|
|
562
|
+ self._parse_length(width, False, False, 1, 1000, width_cb)
|
|
|
563
|
+ if height:
|
|
|
564
|
+ self._parse_length(height, False, False, 1, 1000, height_cb)
|
|
|
565
|
+
|
|
|
566
|
+ def set_size(_pixbuf, width_, height_, dims):
|
|
558
|
567
|
"""
|
|
559
|
568
|
FIXME: Floats should be relative to the whole textview, and
|
|
560
|
569
|
resize with it. This needs new pifbufs for every resize,
|
|
561
|
570
|
GdkPixbuf.Pixbuf.scale_simple or similar.
|
|
562
|
571
|
"""
|
|
563
|
572
|
if isinstance(dims[0], float):
|
|
564
|
|
- dims[0] = int(dims[0]*w)
|
|
|
573
|
+ dims[0] = int(dims[0] * width_)
|
|
565
|
574
|
elif not dims[0]:
|
|
566
|
|
- dims[0] = w
|
|
|
575
|
+ dims[0] = width_
|
|
567
|
576
|
if isinstance(dims[1], float):
|
|
568
|
|
- dims[1] = int(dims[1]*h)
|
|
|
577
|
+ dims[1] = int(dims[1] * height_)
|
|
569
|
578
|
if not dims[1]:
|
|
570
|
|
- dims[1] = h
|
|
|
579
|
+ dims[1] = height_
|
|
571
|
580
|
loader.set_size(*dims)
|
|
572
|
|
- if w or h:
|
|
|
581
|
+
|
|
|
582
|
+ if width or height:
|
|
573
|
583
|
loader.connect('size-prepared', set_size, dims)
|
|
574
|
584
|
loader.write(mem)
|
|
575
|
585
|
loader.close()
|
| ... |
... |
@@ -621,12 +631,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
621
|
631
|
attr = attr.lower()
|
|
622
|
632
|
val = val
|
|
623
|
633
|
try:
|
|
624
|
|
- method = self.__style_methods[attr]
|
|
|
634
|
+ getattr(self, self.__style_methods[attr])(tag, val)
|
|
625
|
635
|
except KeyError:
|
|
626
|
636
|
log.warning('Style attribute "%s" requested '
|
|
627
|
637
|
'but not yet implemented', attr)
|
|
628
|
|
- else:
|
|
629
|
|
- method(self, tag, val)
|
|
630
|
638
|
self.styles.append(tag)
|
|
631
|
639
|
|
|
632
|
640
|
def _end_span(self):
|
| ... |
... |
@@ -660,7 +668,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
660
|
668
|
else:
|
|
661
|
669
|
self._insert_text(text.strip('\n'))
|
|
662
|
670
|
|
|
663
|
|
- def _anchor_event(self, tag, textview, event, iter_, href, type_):
|
|
|
671
|
+ def _anchor_event(self, _tag, _textview, event, _iter, href, type_):
|
|
664
|
672
|
if event.type == Gdk.EventType.BUTTON_PRESS:
|
|
665
|
673
|
self.textview.emit('url-clicked', href, type_)
|
|
666
|
674
|
return True
|
| ... |
... |
@@ -668,8 +676,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
668
|
676
|
|
|
669
|
677
|
def handle_specials(self, text):
|
|
670
|
678
|
if self.conv_textview:
|
|
671
|
|
- self.iter = self.conv_textview.detect_and_print_special_text(text,
|
|
672
|
|
- self._get_style_tags(), iter_=self.iter)
|
|
|
679
|
+ self.iter = self.conv_textview.detect_and_print_special_text(
|
|
|
680
|
+ text, self._get_style_tags(), iter_=self.iter)
|
|
673
|
681
|
else:
|
|
674
|
682
|
self._insert_text(text)
|
|
675
|
683
|
|
| ... |
... |
@@ -682,7 +690,6 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
682
|
690
|
self.text += content
|
|
683
|
691
|
self.starting = False
|
|
684
|
692
|
|
|
685
|
|
-
|
|
686
|
693
|
def startElement(self, name, attrs):
|
|
687
|
694
|
self._flush_text()
|
|
688
|
695
|
klass = [i for i in attrs.get('class', ' ').split(' ') if i]
|
| ... |
... |
@@ -767,10 +774,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
767
|
774
|
log.warning('Unhandled element "%s"', name)
|
|
768
|
775
|
|
|
769
|
776
|
def endElement(self, name):
|
|
770
|
|
- endPreserving = False
|
|
771
|
|
- newLine = False
|
|
|
777
|
+ end_preserving = False
|
|
|
778
|
+ newline = False
|
|
772
|
779
|
if name == 'br':
|
|
773
|
|
- newLine = True
|
|
|
780
|
+ newline = True
|
|
774
|
781
|
elif name == 'hr':
|
|
775
|
782
|
#FIXME: plenty of unused attributes (width, height,...) :)
|
|
776
|
783
|
self._jump_line()
|
| ... |
... |
@@ -779,7 +786,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
779
|
786
|
elif name in LIST_ELEMS:
|
|
780
|
787
|
self.list_counters.pop()
|
|
781
|
788
|
elif name == 'li':
|
|
782
|
|
- newLine = True
|
|
|
789
|
+ newline = True
|
|
783
|
790
|
elif name == 'img':
|
|
784
|
791
|
pass
|
|
785
|
792
|
elif name in ('body', 'html'):
|
| ... |
... |
@@ -792,73 +799,62 @@ class HtmlHandler(xml.sax.handler.ContentHandler): |
|
792
|
799
|
pass
|
|
793
|
800
|
elif name in BLOCK:
|
|
794
|
801
|
if name == 'pre':
|
|
795
|
|
- endPreserving = True
|
|
|
802
|
+ end_preserving = True
|
|
796
|
803
|
elif name in BLOCK_STRUCT:
|
|
797
|
|
- newLine = True
|
|
|
804
|
+ newline = True
|
|
798
|
805
|
else:
|
|
799
|
806
|
log.warning("Unhandled element '%s'", name)
|
|
800
|
807
|
self._flush_text()
|
|
801
|
|
- if endPreserving:
|
|
|
808
|
+ if end_preserving:
|
|
802
|
809
|
self.preserve = False
|
|
803
|
|
- if newLine:
|
|
|
810
|
+ if newline:
|
|
804
|
811
|
self._jump_line()
|
|
805
|
812
|
self._end_span()
|
|
806
|
813
|
|
|
807
|
814
|
|
|
808
|
815
|
class HtmlTextView(Gtk.TextView):
|
|
809
|
816
|
|
|
810
|
|
- def __init__(self, account=None):
|
|
|
817
|
+ _tags = ['url', 'mail', 'xmpp', 'sth_at_sth']
|
|
|
818
|
+
|
|
|
819
|
+ def __init__(self, account, standalone=False):
|
|
811
|
820
|
Gtk.TextView.__init__(self)
|
|
812
|
821
|
self.set_wrap_mode(Gtk.WrapMode.CHAR)
|
|
813
|
822
|
self.set_editable(False)
|
|
814
|
|
- self._changed_cursor = False
|
|
815
|
823
|
self.set_has_tooltip(True)
|
|
816
|
|
- self.connect('realize', self.on_html_text_view_realized)
|
|
817
|
|
- self.connect('unrealize', self.on_html_text_view_unrealized)
|
|
818
|
|
- self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
|
|
819
|
|
- self.id_ = self.connect('button-release-event',
|
|
820
|
|
- self.on_left_mouse_button_release)
|
|
|
824
|
+ self.connect('copy-clipboard', self._on_copy_clipboard)
|
|
821
|
825
|
self.get_buffer().eol_tag = self.get_buffer().create_tag('eol')
|
|
822
|
|
- self.config = app.config
|
|
823
|
|
- self.interface = app.interface
|
|
|
826
|
+
|
|
824
|
827
|
self.account = account
|
|
825
|
828
|
self.plugin_modified = False
|
|
|
829
|
+ self._cursor_changed = False
|
|
826
|
830
|
|
|
827
|
|
- def connect_tooltip(self, func=None):
|
|
828
|
|
- self.connect('query-tooltip', func or self.__query_tooltip)
|
|
|
831
|
+ if standalone:
|
|
|
832
|
+ self.connect('query-tooltip', self._query_tooltip)
|
|
|
833
|
+ self.create_tags()
|
|
829
|
834
|
|
|
830
|
835
|
def create_tags(self):
|
|
831
|
|
- buffer_ = self.get_buffer()
|
|
|
836
|
+ color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR)
|
|
|
837
|
+
|
|
|
838
|
+ attrs = {'foreground': color,
|
|
|
839
|
+ 'underline': Pango.Underline.SINGLE}
|
|
|
840
|
+
|
|
|
841
|
+ for tag in self._tags:
|
|
|
842
|
+ tag_ = self.get_buffer().create_tag(tag, **attrs)
|
|
|
843
|
+ tag_.connect('event', self.hyperlink_handler, tag)
|
|
832
|
844
|
|
|
833
|
|
- self.tagURL = buffer_.create_tag('url')
|
|
|
845
|
+ def update_tags(self):
|
|
|
846
|
+ tag_table = self.get_buffer().get_tag_table()
|
|
834
|
847
|
color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR)
|
|
835
|
|
- self.tagURL.set_property('foreground', color)
|
|
836
|
|
- self.tagURL.set_property('underline', Pango.Underline.SINGLE)
|
|
837
|
|
- self.tagURL.connect('event', self.hyperlink_handler, 'url')
|
|
838
|
|
-
|
|
839
|
|
- self.tagMail = buffer_.create_tag('mail')
|
|
840
|
|
- self.tagMail.set_property('foreground', color)
|
|
841
|
|
- self.tagMail.set_property('underline', Pango.Underline.SINGLE)
|
|
842
|
|
- self.tagMail.connect('event', self.hyperlink_handler, 'mail')
|
|
843
|
|
-
|
|
844
|
|
- self.tagXMPP = buffer_.create_tag('xmpp')
|
|
845
|
|
- self.tagXMPP.set_property('foreground', color)
|
|
846
|
|
- self.tagXMPP.set_property('underline', Pango.Underline.SINGLE)
|
|
847
|
|
- self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp')
|
|
848
|
|
-
|
|
849
|
|
- self.tagSthAtSth = buffer_.create_tag('sth_at_sth')
|
|
850
|
|
- self.tagSthAtSth.set_property('foreground', color)
|
|
851
|
|
- self.tagSthAtSth.set_property('underline', Pango.Underline.SINGLE)
|
|
852
|
|
- self.tagSthAtSth.connect('event', self.hyperlink_handler, 'sth_at_sth')
|
|
853
|
|
-
|
|
854
|
|
- def __query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
|
|
|
848
|
+
|
|
|
849
|
+ for tag in self._tags:
|
|
|
850
|
+ tag_table.lookup(tag).set_property('foreground', color)
|
|
|
851
|
+
|
|
|
852
|
+ def _query_tooltip(self, widget, x_pos, y_pos, _keyboard_mode, tooltip):
|
|
855
|
853
|
window = widget.get_window(Gtk.TextWindowType.TEXT)
|
|
856
|
854
|
x_pos, y_pos = self.window_to_buffer_coords(
|
|
857
|
855
|
Gtk.TextWindowType.TEXT, x_pos, y_pos)
|
|
858
|
|
- if Gtk.MINOR_VERSION > 18:
|
|
859
|
|
- iter_ = self.get_iter_at_position(x_pos, y_pos)[1]
|
|
860
|
|
- else:
|
|
861
|
|
- iter_ = self.get_iter_at_position(x_pos, y_pos)[0]
|
|
|
856
|
+ iter_ = self.get_iter_at_position(x_pos, y_pos)[1]
|
|
|
857
|
+
|
|
862
|
858
|
for tag in iter_.get_tags():
|
|
863
|
859
|
if getattr(tag, 'is_anchor', False):
|
|
864
|
860
|
text = getattr(tag, 'title', False)
|
| ... |
... |
@@ -866,13 +862,19 @@ class HtmlTextView(Gtk.TextView): |
|
866
|
862
|
if len(text) > 50:
|
|
867
|
863
|
text = text[:47] + '…'
|
|
868
|
864
|
tooltip.set_text(text)
|
|
869
|
|
- if not self._changed_cursor:
|
|
870
|
865
|
window.set_cursor(get_cursor('HAND2'))
|
|
871
|
|
- self._changed_cursor = True
|
|
872
|
|
- return True
|
|
873
|
|
- if self._changed_cursor:
|
|
|
866
|
+ self._cursor_changed = True
|
|
|
867
|
+ return True
|
|
|
868
|
+
|
|
|
869
|
+ tag_name = tag.get_property('name')
|
|
|
870
|
+ if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
|
|
|
871
|
+ window.set_cursor(get_cursor('HAND2'))
|
|
|
872
|
+ self._cursor_changed = True
|
|
|
873
|
+ return False
|
|
|
874
|
+
|
|
|
875
|
+ if self._cursor_changed:
|
|
874
|
876
|
window.set_cursor(get_cursor('XTERM'))
|
|
875
|
|
- self._changed_cursor = False
|
|
|
877
|
+ self._cursor_changed = False
|
|
876
|
878
|
return False
|
|
877
|
879
|
|
|
878
|
880
|
def show_context_menu(self, _event, kind, text):
|
| ... |
... |
@@ -880,7 +882,7 @@ class HtmlTextView(Gtk.TextView): |
|
880
|
882
|
if menu is None:
|
|
881
|
883
|
return
|
|
882
|
884
|
|
|
883
|
|
- def destroy(menu, pspec):
|
|
|
885
|
+ def destroy(menu, _pspec):
|
|
884
|
886
|
visible = menu.get_property('visible')
|
|
885
|
887
|
if not visible:
|
|
886
|
888
|
GLib.idle_add(menu.destroy)
|
| ... |
... |
@@ -893,7 +895,7 @@ class HtmlTextView(Gtk.TextView): |
|
893
|
895
|
if event.type == Gdk.EventType.BUTTON_PRESS:
|
|
894
|
896
|
begin_iter = iter_.copy()
|
|
895
|
897
|
# we get the beginning of the tag
|
|
896
|
|
- while not begin_iter.begins_tag(texttag):
|
|
|
898
|
+ while not begin_iter.starts_tag(texttag):
|
|
897
|
899
|
begin_iter.backward_char()
|
|
898
|
900
|
end_iter = iter_.copy()
|
|
899
|
901
|
# we get the end of the tag
|
| ... |
... |
@@ -946,8 +948,8 @@ class HtmlTextView(Gtk.TextView): |
|
946
|
948
|
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % \
|
|
947
|
949
|
{'lat': lat, 'lon': lon}
|
|
948
|
950
|
helpers.launch_browser_mailer(kind, uri)
|
|
949
|
|
- # other URIs
|
|
950
|
951
|
else:
|
|
|
952
|
+ # other URIs
|
|
951
|
953
|
helpers.launch_browser_mailer(kind, word)
|
|
952
|
954
|
|
|
953
|
955
|
def display_html(self, html, textview, conv_textview, iter_=None):
|
| ... |
... |
@@ -956,42 +958,16 @@ class HtmlTextView(Gtk.TextView): |
|
956
|
958
|
eob = iter_
|
|
957
|
959
|
else:
|
|
958
|
960
|
eob = buffer_.get_end_iter()
|
|
959
|
|
- ## this works too if libxml2 is not available
|
|
960
|
|
- # parser = xml.sax.make_parser(['drv_libxml2'])
|
|
961
|
|
- # parser.setFeature(xml.sax.handler.feature_validation, True)
|
|
962
|
961
|
parser = xml.sax.make_parser()
|
|
963
|
962
|
parser.setContentHandler(HtmlHandler(textview, conv_textview, eob))
|
|
964
|
963
|
parser.parse(StringIO(html))
|
|
965
|
964
|
|
|
966
|
|
- # too much space after :)
|
|
967
|
|
- #if not eob.starts_line():
|
|
968
|
|
- # buffer_.insert(eob, '\n')
|
|
969
|
|
-
|
|
970
|
|
- def on_html_text_view_copy_clipboard(self, unused_data):
|
|
971
|
|
- clipboard = self.get_clipboard(Gdk.SELECTION_CLIPBOARD)
|
|
972
|
|
- selected = self.get_selected_text()
|
|
|
965
|
+ @staticmethod
|
|
|
966
|
+ def _on_copy_clipboard(textview):
|
|
|
967
|
+ clipboard = textview.get_clipboard(Gdk.SELECTION_CLIPBOARD)
|
|
|
968
|
+ selected = textview.get_selected_text()
|
|
973
|
969
|
clipboard.set_text(selected, -1)
|
|
974
|
|
- GObject.signal_stop_emission_by_name(self, 'copy-clipboard')
|
|
975
|
|
-
|
|
976
|
|
- def on_html_text_view_realized(self, unused_data):
|
|
977
|
|
- self.get_buffer().remove_selection_clipboard(self.get_clipboard(
|
|
978
|
|
- Gdk.SELECTION_PRIMARY))
|
|
979
|
|
-
|
|
980
|
|
- def on_html_text_view_unrealized(self, unused_data):
|
|
981
|
|
- self.get_buffer().add_selection_clipboard(self.get_clipboard(
|
|
982
|
|
- Gdk.SELECTION_PRIMARY))
|
|
983
|
|
-
|
|
984
|
|
- def on_left_mouse_button_release(self, widget, event):
|
|
985
|
|
- if event.button != 1:
|
|
986
|
|
- return
|
|
987
|
|
-
|
|
988
|
|
- bounds = self.get_buffer().get_selection_bounds()
|
|
989
|
|
- if bounds:
|
|
990
|
|
- # textview can be hidden while we add a new line in it.
|
|
991
|
|
- if self.has_screen():
|
|
992
|
|
- clipboard = self.get_clipboard(Gdk.SELECTION_PRIMARY)
|
|
993
|
|
- selected = self.get_selected_text()
|
|
994
|
|
- clipboard.set_text(selected, -1)
|
|
|
970
|
+ GObject.signal_stop_emission_by_name(textview, 'copy-clipboard')
|
|
995
|
971
|
|
|
996
|
972
|
def get_selected_text(self):
|
|
997
|
973
|
bounds = self.get_buffer().get_selection_bounds()
|