~ubuntu-branches/ubuntu/wily/gajim/wily

« back to all changes in this revision

Viewing changes to src/htmltextview.py

  • Committer: Bazaar Package Importer
  • Author(s): Yann Leboulanger
  • Date: 2011-06-07 19:30:43 UTC
  • mfrom: (10.1.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20110607193043-hmjwviw7ws7mcx94
Tags: 0.14.2-1
* New upstream release.
* Fix CPU usage when testing file transfer proxies. Closes: #626576

Show diffs side-by-side

added added

removed removed

Lines of Context:
40
40
import gtk
41
41
import xml.sax, xml.sax.handler
42
42
import re
43
 
import warnings
44
43
from cStringIO import StringIO
45
44
import socket
46
45
import time
50
49
if __name__ == '__main__':
51
50
    from common import i18n
52
51
    import common.configpaths
 
52
    common.configpaths.gajimpaths.init_profile()
53
53
    common.configpaths.gajimpaths.init(None)
 
54
    import gtkgui_helpers
54
55
from common import gajim
55
56
 
56
57
import tooltips
57
 
 
 
58
import logging
 
59
log = logging.getLogger('gajim.htmlview')
58
60
 
59
61
__all__ = ['HtmlTextView']
60
62
 
182
184
    size = (num-1) // 2
183
185
    weigth = (num - 1) % 2
184
186
    element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
185
 
                                                                                                    ('font-weight: bold', 'font-style: oblique')[weigth],
186
 
                                                                                      )
 
187
        ('font-weight: bold', 'font-style: oblique')[weigth],)
187
188
 
188
189
def _parse_css_color(color):
189
190
    if color.startswith('rgb(') and color.endswith(')'):
215
216
        self.preserve = False
216
217
        self.styles = [] # a gtk.TextTag or None, for each span level
217
218
        self.list_counters = [] # stack (top at head) of list
218
 
                                                        # counters, or None for unordered list
 
219
                                # counters, or None for unordered list
219
220
 
220
221
    def _parse_style_color(self, tag, value):
221
222
        color = _parse_css_color(value)
234
235
        self.iter.forward_char()
235
236
        return attrs
236
237
 
237
 
    def __parse_length_frac_size_allocate(self, textview, allocation,
238
 
                                                                              frac, callback, args):
 
238
    def __parse_length_frac_size_allocate(self, textview, allocation, frac,
 
239
        callback, args):
239
240
        callback(allocation.width*frac, *args)
240
241
 
241
 
    def _parse_length(self, value, font_relative, block_relative, minl, maxl, callback, *args):
 
242
    def _parse_length(self, value, font_relative, block_relative, minl, maxl,
 
243
        callback, *args):
242
244
        """
243
245
        Parse/calc length, converting to pixels, calls callback(length, *args)
244
246
        when the length is first computed or changes
260
262
                # textview width instead; a reasonable approximation..
261
263
                alloc = self.textview.get_allocation()
262
264
                self.__parse_length_frac_size_allocate(self.textview, alloc,
263
 
                                                                                           frac, callback, args)
 
265
                    frac, callback, args)
264
266
                self.textview.connect('size-allocate',
265
 
                                                          self.__parse_length_frac_size_allocate,
266
 
                                                          frac, callback, args)
 
267
                    self.__parse_length_frac_size_allocate,
 
268
                    frac, callback, args)
267
269
            else:
268
270
                callback(frac, *args)
269
271
            return
300
302
                val = sign*max(minl, min(abs(val), maxl))
301
303
                callback(val, *args)
302
304
            except Exception:
303
 
                warnings.warn('Unable to parse length value "%s"' % value)
 
305
                log.warning('Unable to parse length value "%s"' % value)
304
306
 
305
307
    def __parse_font_size_cb(length, tag):
306
308
        tag.set_property('size-points', length/display_resolution)
335
337
            tag.set_property('scale', pango.SCALE_LARGE)
336
338
            return
337
339
        # font relative (5 ~ 4pt, 110 ~ 72pt)
338
 
        self._parse_length(value, True, False, 5, 110, self.__parse_font_size_cb, tag)
 
340
        self._parse_length(value, True, False, 5, 110,self.__parse_font_size_cb,
 
341
            tag)
339
342
 
340
343
    def _parse_style_font_style(self, tag, value):
341
344
        try:
345
348
                    'oblique': pango.STYLE_OBLIQUE,
346
349
                    } [value]
347
350
        except KeyError:
348
 
            warnings.warn('unknown font-style %s' % value)
 
351
            log.warning('unknown font-style %s' % value)
349
352
        else:
350
353
            tag.set_property('style', style)
351
354
 
358
361
 
359
362
    def _parse_style_margin_left(self, tag, value):
360
363
        # block relative
361
 
        self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
362
 
                                           tag, 'left-margin')
 
364
        self._parse_length(value, False, True, 1, 1000,
 
365
            self.__frac_length_tag_cb, tag, 'left-margin')
363
366
 
364
367
    def _parse_style_margin_right(self, tag, value):
365
368
        # block relative
366
 
        self._parse_length(value, False, True, 1, 1000, self.__frac_length_tag_cb,
367
 
                                           tag, 'right-margin')
 
369
        self._parse_length(value, False, True, 1, 1000,
 
370
            self.__frac_length_tag_cb, tag, 'right-margin')
368
371
 
369
372
    def _parse_style_font_weight(self, tag, value):
370
373
        # TODO: missing 'bolder' and 'lighter'
383
386
                    'bold': pango.WEIGHT_BOLD,
384
387
                    } [value]
385
388
        except KeyError:
386
 
            warnings.warn('unknown font-style %s' % value)
 
389
            log.warning('unknown font-style %s' % value)
387
390
        else:
388
391
            tag.set_property('weight', weight)
389
392
 
399
402
                    'justify': gtk.JUSTIFY_FILL,
400
403
                    } [value]
401
404
        except KeyError:
402
 
            warnings.warn('Invalid text-align:%s requested' % value)
 
405
            log.warning('Invalid text-align:%s requested' % value)
403
406
        else:
404
407
            tag.set_property('justification', align)
405
408
 
417
420
        else:
418
421
            tag.set_property('strikethrough', False)
419
422
        if 'blink' in values:
420
 
            warnings.warn('text-decoration:blink not implemented')
 
423
            log.warning('text-decoration:blink not implemented')
421
424
        if 'overline' in values:
422
 
            warnings.warn('text-decoration:overline not implemented')
 
425
            log.warning('text-decoration:overline not implemented')
423
426
 
424
427
    def _parse_style_white_space(self, tag, value):
425
428
        if value == 'pre':
433
436
        try:
434
437
            tag.set_property(propname, value)
435
438
        except Exception:
436
 
            gajim.log.warn( "Error with prop: " + propname + " for tag: " + str(tag))
 
439
            log.warning( "Error with prop: " + propname + " for tag: " + str(tag))
437
440
 
438
441
 
439
442
    def _parse_style_width(self, tag, value):
440
443
        if value == 'auto':
441
444
            return
442
445
        self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
443
 
                                           tag, "width")
 
446
            tag, "width")
444
447
    def _parse_style_height(self, tag, value):
445
448
        if value == 'auto':
446
449
            return
447
450
        self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
448
 
                                           tag, "height")
 
451
            tag, "height")
449
452
 
450
453
 
451
454
    # build a dictionary mapping styles to methods, for greater speed
452
455
    __style_methods = dict()
453
456
    for style in ('background-color', 'color', 'font-family', 'font-size',
454
 
                              'font-style', 'font-weight', 'margin-left', 'margin-right',
455
 
                              'text-align', 'text-decoration', 'white-space', 'display',
456
 
                              'width', 'height' ):
 
457
                  'font-style', 'font-weight', 'margin-left', 'margin-right',
 
458
                  'text-align', 'text-decoration', 'white-space', 'display',
 
459
                  'width', 'height' ):
457
460
        try:
458
461
            method = locals()['_parse_style_%s' % style.replace('-', '_')]
459
462
        except KeyError:
460
 
            warnings.warn('Style attribute "%s" not yet implemented' % style)
 
463
            log.warning('Style attribute "%s" not yet implemented' % style)
461
464
        else:
462
465
            __style_methods[style] = method
463
466
    del style
473
476
        if href and href[0] != '#':
474
477
            tag.href = href
475
478
            tag.type_ = type_ # to be used by the URL handler
476
 
            tag.connect('event', self.textview.html_hyperlink_handler, 'url', href)
 
479
            tag.connect('event', self.textview.hyperlink_handler, 'url')
477
480
            tag.set_property('foreground', gajim.config.get('urlmsgcolor'))
478
481
            tag.set_property('underline', pango.UNDERLINE_SINGLE)
479
482
            tag.is_anchor = True
493
496
                req.add_header('User-Agent', 'Gajim ' + gajim.version)
494
497
                f = urllib2.urlopen(req)
495
498
            except Exception, ex:
496
 
                gajim.log.debug('Error loading image %s ' % attrs['src']  + str(ex))
 
499
                log.debug('Error loading image %s ' % attrs['src']  + str(ex))
497
500
                pixbuf = None
498
501
                alt = attrs.get('alt', 'Broken image')
499
502
            else:
506
509
                deadline = time.time() + 3
507
510
                while True:
508
511
                    if time.time() > deadline:
509
 
                        gajim.log.debug(str('Timeout loading image %s ' % \
510
 
                                attrs['src'] + ex))
 
512
                        log.debug(str('Timeout loading image %s ' % \
 
513
                            attrs['src'] + ex))
511
514
                        mem = ''
512
515
                        alt = attrs.get('alt', '')
513
516
                        if alt:
517
520
                    try:
518
521
                        temp = f.read(100)
519
522
                    except socket.timeout, ex:
520
 
                        gajim.log.debug('Timeout loading image %s ' % attrs['src'] + \
521
 
                                str(ex))
 
523
                        log.debug('Timeout loading image %s ' % \
 
524
                            attrs['src'] + str(ex))
522
525
                        alt = attrs.get('alt', '')
523
526
                        if alt:
524
527
                            alt += '\n'
593
596
            else:
594
597
                self._insert_text('[IMG: %s]' % alt)
595
598
        except Exception, ex:
596
 
            gajim.log.error('Error loading image ' + str(ex))
 
599
            log.error('Error loading image ' + str(ex))
597
600
            pixbuf = None
598
601
            alt = attrs.get('alt', 'Broken image')
599
602
            try:
617
620
            try:
618
621
                method = self.__style_methods[attr]
619
622
            except KeyError:
620
 
                warnings.warn('Style attribute "%s" requested '
621
 
                                          'but not yet implemented' % attr)
 
623
                log.warning('Style attribute "%s" requested '
 
624
                    'but not yet implemented' % attr)
622
625
            else:
623
626
                method(self, tag, val)
624
627
        self.styles.append(tag)
658
661
        return False
659
662
 
660
663
    def handle_specials(self, text):
661
 
        self.iter = self.conv_textview.detect_and_print_special_text(text,                                                      self._get_style_tags())
 
664
        self.iter = self.conv_textview.detect_and_print_special_text(text,
 
665
            self._get_style_tags())
662
666
 
663
667
    def characters(self, content):
664
668
        if self.preserve:
665
669
            self.text += content
666
670
            return
667
671
        if allwhitespace_rx.match(content) is not None and self._starts_line():
668
 
            self.text += ' '
669
672
            return
670
673
        self.text += content
671
674
        self.starting = False
750
753
        elif name in ('a', 'img', 'body', 'html'):
751
754
            pass
752
755
        elif name in INLINE:
753
 
            pass
 
756
            self._jump_line()
754
757
        else:
755
 
            warnings.warn('Unhandled element "%s"' % name)
 
758
            log.warning('Unhandled element "%s"' % name)
756
759
 
757
760
    def endElement(self, name):
758
761
        endPreserving = False
763
766
            #FIXME: plenty of unused attributes (width, height,...) :)
764
767
            self._jump_line()
765
768
            try:
766
 
                self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
 
769
                self.textbuf.insert_pixbuf(self.iter,
 
770
                    self.textview.focus_out_line_pixbuf)
767
771
                #self._insert_text(u'\u2550'*40)
768
772
                self._jump_line()
769
773
            except Exception, e:
770
 
                gajim.log.debug(str('Error in hr'+e))
 
774
                log.debug(str('Error in hr'+e))
771
775
        elif name in LIST_ELEMS:
772
776
            self.list_counters.pop()
773
777
        elif name == 'li':
786
790
            if name == 'pre':
787
791
                endPreserving = True
788
792
        else:
789
 
            warnings.warn("Unhandled element '%s'" % name)
 
793
            log.warning("Unhandled element '%s'" % name)
790
794
        self._flush_text()
791
795
        if endPreserving:
792
796
            self.preserve = False
859
863
            window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
860
864
            window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
861
865
            self._changed_cursor = True
862
 
            self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip, anchor_tags[0])
 
866
            self.tooltip.timeout = gobject.timeout_add(500, self.show_tooltip,
 
867
                anchor_tags[0])
863
868
        elif self._changed_cursor and not anchor_tags:
864
869
            window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
865
870
            window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
886
891
        self.emit_stop_by_name('copy-clipboard')
887
892
 
888
893
    def on_html_text_view_realized(self, unused_data):
889
 
        self.get_buffer().remove_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
 
894
        self.get_buffer().remove_selection_clipboard(self.get_clipboard(
 
895
            gtk.gdk.SELECTION_PRIMARY))
890
896
 
891
897
    def on_html_text_view_unrealized(self, unused_data):
892
 
        self.get_buffer().add_selection_clipboard(self.get_clipboard(gtk.gdk.SELECTION_PRIMARY))
 
898
        self.get_buffer().add_selection_clipboard(self.get_clipboard(
 
899
            gtk.gdk.SELECTION_PRIMARY))
893
900
 
894
901
    def on_text_buffer_mark_set(self, location, mark, unused_data):
895
902
        bounds = self.get_buffer().get_selection_bounds()
928
935
    from conversation_textview import ConversationTextview
929
936
    import gajim as gaj
930
937
 
931
 
    class log(object):
932
 
 
933
 
        def debug(self, text):
934
 
            print "debug:", text
935
 
        def warn(self, text):
936
 
            print "warn;", text
937
 
        def error(self, text):
938
 
            print "error;", text
939
 
 
940
 
    gajim.log=log()
941
 
 
 
938
    log = logging.getLogger()
942
939
    gaj.Interface()
943
940
 
944
941
    htmlview = ConversationTextview(None)
955
952
        """
956
953
        global change_cursor
957
954
        pointer_x, pointer_y = htmlview.tv.window.get_pointer()[0:2]
958
 
        x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
959
 
                                                           pointer_y)
 
955
        x, y = htmlview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
 
956
            pointer_x, pointer_y)
960
957
        tags = htmlview.tv.get_iter_at_location(x, y).get_tags()
961
958
        if change_cursor:
962
959
            htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
963
 
                             gtk.gdk.Cursor(gtk.gdk.XTERM))
 
960
                gtk.gdk.Cursor(gtk.gdk.XTERM))
964
961
            change_cursor = None
965
962
        tag_table = htmlview.tv.get_buffer().get_tag_table()
966
963
        for tag in tags:
967
964
            try:
968
965
                if tag.is_anchor:
969
966
                    htmlview.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
970
 
                                                            gtk.gdk.Cursor(gtk.gdk.HAND2))
 
967
                        gtk.gdk.Cursor(gtk.gdk.HAND2))
971
968
                    change_cursor = tag
972
969
                elif tag == tag_table.lookup('focus-out-line'):
973
970
                    over_line = True
987
984
 
988
985
    htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event)
989
986
 
990
 
    def handler(texttag, widget, event, iter_, kind, href):
 
987
    def handler(texttag, widget, event, iter_, kind):
991
988
        if event.type == gtk.gdk.BUTTON_PRESS:
992
 
            print href
993
 
 
994
 
    htmlview.tv.html_hyperlink_handler = handler
995
 
 
996
 
    htmlview.print_real_text(None, xhtml='<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
997
 
                                              '  <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
998
 
                                              '  <span style="font-size: 500%; font-family: serif">World</span>\n'
999
 
                                              '</div>\n')
 
989
            pass
 
990
 
 
991
    htmlview.tv.hyperlink_handler = htmlview.hyperlink_handler
 
992
 
 
993
    htmlview.print_real_text(None, xhtml='<div>'
 
994
    '<span style="color: red; text-decoration:underline">Hello</span><br/>\n'
 
995
      '  <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
 
996
    '<span style="font-size: 500%; font-family: serif">World</span>\n'
 
997
      '</div>\n')
1000
998
    htmlview.print_real_text(None, xhtml='<hr />')
1001
 
    htmlview.print_real_text(None, xhtml='''<body xmlns='http://www.w3.org/1999/xhtml'><p xmlns='http://www.w3.org/1999/xhtml'>a:b<a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google</a></p><br/></body>''')
 
999
    htmlview.print_real_text(None, xhtml='''
 
1000
    <body xmlns='http://www.w3.org/1999/xhtml'>
 
1001
     <p xmlns='http://www.w3.org/1999/xhtml'>a:b
 
1002
       <a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google
 
1003
       </a>
 
1004
     </p><br/>
 
1005
    </body>''')
1002
1006
    htmlview.print_real_text(None, xhtml='''
1003
1007
     <body xmlns='http://www.w3.org/1999/xhtml'>
1004
1008
      <p style='font-size:large'>
1005
 
            <span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>,
 
1009
            <span style='font-style: italic'>O
 
1010
            <span style='font-size:larger'>M</span>G</span>,
1006
1011
            I&apos;m <span style='color:green'>green</span>
1007
1012
            with <span style='font-weight: bold'>envy</span>!
1008
1013
      </p>
1017
1022
    htmlview.print_real_text(None, xhtml='<hr />')
1018
1023
    htmlview.print_real_text(None, xhtml='''
1019
1024
    <body xmlns='http://www.w3.org/1999/xhtml'>
1020
 
      <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
 
1025
      <p>As Emerson said in his essay <span style='
 
1026
        font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
1021
1027
      <p style='margin-left: 5px; margin-right: 2%'>
1022
1028
            &quot;A foolish consistency is the hobgoblin of little minds.&quot;
1023
1029
      </p>
1026
1032
    htmlview.print_real_text(None, xhtml='<hr />')
1027
1033
    htmlview.print_real_text(None, xhtml='''
1028
1034
    <body xmlns='http://www.w3.org/1999/xhtml'>
1029
 
      <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
1030
 
      <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
1031
 
                      alt='A License to Jabber'
1032
 
                      width='50%' height='50%'
1033
 
                      /></p>
 
1035
      <p style='text-align:center'>
 
1036
        Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?
 
1037
      </p>
 
1038
      <p style='text-align:right'>
 
1039
        <img src='http://www.xmpp.org/images/psa-license.jpg'
 
1040
        alt='A License to Jabber' width='50%' height='50%'/>
 
1041
      </p>
1034
1042
    </body>
1035
1043
            ''')
1036
1044
    htmlview.print_real_text(None, xhtml='<hr />')
1062
1070
       <li> Three </li></ol>
1063
1071
    </body>
1064
1072
            ''')
 
1073
    htmlview.print_real_text(None, xhtml='<hr />')
 
1074
    htmlview.print_real_text(None, xhtml='''
 
1075
    <body xmlns='http://www.w3.org/1999/xhtml'>
 
1076
    <p>
 
1077
      <strong>
 
1078
        <a href='xmpp:example@example.org'>xmpp link</a>
 
1079
      </strong>: </p>
 
1080
    <div xmlns='http://www.w3.org/1999/xhtml'>
 
1081
      <cite style='margin: 7px;' title='xmpp:examples@example.org'>
 
1082
        <p>
 
1083
          <strong>examples@example.org wrote:</strong>
 
1084
        </p>
 
1085
        <p>this cite - bla bla bla, smile- :-)  ...</p>
 
1086
      </cite>
 
1087
      <div>
 
1088
        <p>some text</p>
 
1089
      </div>
 
1090
    </div>
 
1091
    <p/>
 
1092
    <p>#232/1</p>
 
1093
    </body>
 
1094
    ''')
1065
1095
    htmlview.tv.show()
1066
1096
    sw = gtk.ScrolledWindow()
1067
1097
    sw.set_property('hscrollbar-policy', gtk.POLICY_AUTOMATIC)