HtmlTextView: Fix pylint errors and minor refactoring

This commit is contained in:
Philipp Hörist 2018-11-16 18:28:41 +01:00
parent 97310a059a
commit eedd0c2a72
1 changed files with 118 additions and 119 deletions

View File

@ -56,34 +56,32 @@ from gajim.gui_menu_builder import get_conv_context_menu
log = logging.getLogger('gajim.htmlview')
__all__ = ['HtmlTextView']
whitespace_rx = re.compile('\\s+')
allwhitespace_rx = re.compile('^\\s*$')
# embryo of CSS classes
classes = {
#'system-message':';display: none',
'problematic': ';color: red',
#'system-message':';display: none',
'problematic': ';color: red',
}
# styles for elements
_element_styles = {
'u' : ';text-decoration: underline',
'em' : ';font-style: oblique',
'cite' : '; background-color:rgb(170,190,250);'
'font-style: oblique',
'li' : '; margin-left: 1em; margin-right: 10%',
'strong' : ';font-weight: bold',
'pre' : '; background-color:rgb(190,190,190);'
'font-family: monospace; white-space: pre;'
'margin-left: 1em; margin-right: 10%',
'kbd' : ';background-color:rgb(210,210,210);'
'font-family: monospace',
'blockquote' : '; background-color:rgb(170,190,250);'
'margin-left: 2em; margin-right: 10%',
'dt' : ';font-weight: bold; font-style: oblique',
'dd' : ';margin-left: 2em; font-style: oblique'
'u' : ';text-decoration: underline',
'em' : ';font-style: oblique',
'cite' : '; background-color:rgb(170,190,250);'
'font-style: oblique',
'li' : '; margin-left: 1em; margin-right: 10%',
'strong' : ';font-weight: bold',
'pre' : '; background-color:rgb(190,190,190);'
'font-family: monospace; white-space: pre;'
'margin-left: 1em; margin-right: 10%',
'kbd' : ';background-color:rgb(210,210,210);'
'font-family: monospace',
'blockquote' : '; background-color:rgb(170,190,250);'
'margin-left: 2em; margin-right: 10%',
'dt' : ';font-weight: bold; font-style: oblique',
'dd' : ';margin-left: 2em; font-style: oblique'
}
# no difference for the moment
_element_styles['dfn'] = _element_styles['em']
@ -93,6 +91,12 @@ _element_styles['tt'] = _element_styles['kbd']
_element_styles['i'] = _element_styles['em']
_element_styles['b'] = _element_styles['strong']
_supported_style_attrs = [
'background-color', 'color', 'font-family', 'font-size', 'font-style',
'font-weight', 'margin-left', 'margin-right', 'text-align',
'text-decoration', 'white-space', 'display', 'width', 'height'
]
# ==========
# XEP-0071
# ==========
@ -182,8 +186,9 @@ for _name in BLOCK_HEAD:
_num = int(_name[1])
_header_size = (_num - 1) // 2
_weight = (_num - 1) % 2
_element_styles[_name] = '; font-size: %s; %s' % (('large', 'medium', 'small')[_header_size],
('font-weight: bold', 'font-style: oblique')[_weight],)
_element_styles[_name] = '; font-size: %s; %s' % (
('large', 'medium', 'small')[_header_size],
('font-weight: bold', 'font-style: oblique')[_weight])
def _parse_css_color(color):
if color.startswith('rgb(') and color.endswith(')'):
@ -216,26 +221,35 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
self.list_counters = [] # stack (top at head) of list
# counters, or None for unordered list
# build a dictionary mapping styles to methods
self.__style_methods = {}
for style in _supported_style_attrs:
method_names = '_parse_style_%s' % style.replace('-', '_')
self.__style_methods[style] = method_names
def _get_points_from_pixels(self, pixels):
resolution = self.textview.get_screen().get_resolution()
# points = pixels * 72 / resolution
return pixels * 72 / resolution
def _parse_style_color(self, tag, value):
@staticmethod
def _parse_style_color(tag, value):
color = _parse_css_color(value)
tag.set_property('foreground-gdk', color)
def _parse_style_background_color(self, tag, value):
@staticmethod
def _parse_style_background_color(tag, value):
color = _parse_css_color(value)
tag.set_property('background-gdk', color)
tag.set_property('paragraph-background-gdk', color)
def __parse_length_frac_size_allocate(self, textview, allocation, frac,
callback, args):
@staticmethod
def __parse_length_frac_size_allocate(_textview, allocation, frac,
callback, args):
callback(allocation.width*frac, *args)
def _parse_length(self, value, font_relative, block_relative, minl, maxl,
callback, *args):
callback, *args):
"""
Parse/calc length, converting to pixels, calls callback(length, *args)
when the length is first computed or changes
@ -259,11 +273,11 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
# This is difficult/impossible to implement, so we use
# textview width instead; a reasonable approximation..
alloc = self.textview.get_allocation()
self.__parse_length_frac_size_allocate(self.textview, alloc,
frac, callback, args)
self.__parse_length_frac_size_allocate(
self.textview, alloc, frac, callback, args)
self.textview.connect('size-allocate',
self.__parse_length_frac_size_allocate,
frac, callback, args)
self.__parse_length_frac_size_allocate,
frac, callback, args)
else:
callback(frac, *args)
return
@ -324,7 +338,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
elif type_ == 'px':
tag.set_property('size-points', self._get_points_from_pixels(size))
def _parse_style_display(self, tag, value):
@staticmethod
def _parse_style_display(tag, value):
if value == 'none':
tag.set_property('invisible', 'true')
# FIXME: display: block, inline
@ -355,13 +370,14 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
self._parse_length(
value, True, False, 5, 110, self.__parse_font_size_cb, tag)
def _parse_style_font_style(self, tag, value):
@staticmethod
def _parse_style_font_style(tag, value):
try:
style = {
'normal': Pango.Style.NORMAL,
'italic': Pango.Style.ITALIC,
'oblique': Pango.Style.OBLIQUE,
}[value]
'normal': Pango.Style.NORMAL,
'italic': Pango.Style.ITALIC,
'oblique': Pango.Style.OBLIQUE,
}[value]
except KeyError:
log.warning('unknown font-style %s', value)
else:
@ -376,51 +392,55 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
def _parse_style_margin_left(self, tag, value):
# block relative
self._parse_length(value, False, True, 1, 1000,
self.__frac_length_tag_cb, tag, 'left-margin')
self.__frac_length_tag_cb, tag, 'left-margin')
def _parse_style_margin_right(self, tag, value):
# block relative
self._parse_length(value, False, True, 1, 1000,
self.__frac_length_tag_cb, tag, 'right-margin')
self.__frac_length_tag_cb, tag, 'right-margin')
def _parse_style_font_weight(self, tag, value):
@staticmethod
def _parse_style_font_weight(tag, value):
# TODO: missing 'bolder' and 'lighter'
try:
weight = {
'100': Pango.Weight.ULTRALIGHT,
'200': Pango.Weight.ULTRALIGHT,
'300': Pango.Weight.LIGHT,
'400': Pango.Weight.NORMAL,
'500': Pango.Weight.NORMAL,
'600': Pango.Weight.BOLD,
'700': Pango.Weight.BOLD,
'800': Pango.Weight.ULTRABOLD,
'900': Pango.Weight.HEAVY,
'normal': Pango.Weight.NORMAL,
'bold': Pango.Weight.BOLD,
}[value]
'100': Pango.Weight.ULTRALIGHT,
'200': Pango.Weight.ULTRALIGHT,
'300': Pango.Weight.LIGHT,
'400': Pango.Weight.NORMAL,
'500': Pango.Weight.NORMAL,
'600': Pango.Weight.BOLD,
'700': Pango.Weight.BOLD,
'800': Pango.Weight.ULTRABOLD,
'900': Pango.Weight.HEAVY,
'normal': Pango.Weight.NORMAL,
'bold': Pango.Weight.BOLD,
}[value]
except KeyError:
log.warning('unknown font-style %s', value)
else:
tag.set_property('weight', weight)
def _parse_style_font_family(self, tag, value):
@staticmethod
def _parse_style_font_family(tag, value):
tag.set_property('family', value)
def _parse_style_text_align(self, tag, value):
@staticmethod
def _parse_style_text_align(tag, value):
try:
align = {
'left': Gtk.Justification.LEFT,
'right': Gtk.Justification.RIGHT,
'center': Gtk.Justification.CENTER,
'justify': Gtk.Justification.FILL,
}[value]
'left': Gtk.Justification.LEFT,
'right': Gtk.Justification.RIGHT,
'center': Gtk.Justification.CENTER,
'justify': Gtk.Justification.FILL,
}[value]
except KeyError:
log.warning('Invalid text-align: %s requested', value)
else:
tag.set_property('justification', align)
def _parse_style_text_decoration(self, tag, value):
@staticmethod
def _parse_style_text_decoration(tag, value):
values = value.split(' ')
if 'none' in values:
tag.set_property('underline', Pango.Underline.NONE)
@ -438,7 +458,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
if 'overline' in values:
log.warning('text-decoration:overline not implemented')
def _parse_style_white_space(self, tag, value):
@staticmethod
def _parse_style_white_space(tag, value):
if value == 'pre':
tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
elif value == 'normal':
@ -446,7 +467,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
elif value == 'nowrap':
tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
def __length_tag_cb(self, value, tag, propname):
@staticmethod
def __length_tag_cb(value, tag, propname):
try:
tag.set_property(propname, value)
except Exception:
@ -455,29 +477,13 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
def _parse_style_width(self, tag, value):
if value == 'auto':
return
self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
tag, "width")
self._parse_length(value, False, False, 1, 1000,
self.__length_tag_cb, tag, "width")
def _parse_style_height(self, tag, value):
if value == 'auto':
return
self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
tag, "height")
# build a dictionary mapping styles to methods, for greater speed
__style_methods = dict()
for style in ('background-color', 'color', 'font-family', 'font-size',
'font-style', 'font-weight', 'margin-left', 'margin-right',
'text-align', 'text-decoration', 'white-space', 'display',
'width', 'height'):
try:
method = locals()['_parse_style_%s' % style.replace('-', '_')]
except KeyError:
log.warning('Style attribute "%s" not yet implemented', style)
else:
__style_methods[style] = method
del style # pylint: disable=undefined-loop-variable
# --
self._parse_length(value, False, False, 1, 1000,
self.__length_tag_cb, tag, "height")
def _get_style_tags(self):
return [tag for tag in self.styles if tag is not None]
@ -523,8 +529,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
else:
if self.conv_textview:
img_mark = self.textbuf.create_mark(None, self.iter, True)
app.thread_interface(helpers.download_image, [
self.conv_textview.account, attrs], self._update_img,
app.thread_interface(
helpers.download_image,
[self.conv_textview.account, attrs],
self._update_img,
[attrs, img_mark, self._get_style_tags()])
alt = attrs.get('alt', '')
if alt:
@ -542,19 +550,20 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
def width_cb(length):
dims[0] = length
# process width and height attributes
w = attrs.get('width')
h = attrs.get('height')
width = attrs.get('width')
height = attrs.get('height')
# override with width and height styles
for attr, val in style_iter(attrs.get('style', '')):
if attr == 'width':
w = val
width = val
elif attr == 'height':
h = val
if w:
self._parse_length(w, False, False, 1, 1000, width_cb)
if h:
self._parse_length(h, False, False, 1, 1000, height_cb)
def set_size(pixbuf, w, h, dims):
height = val
if width:
self._parse_length(width, False, False, 1, 1000, width_cb)
if height:
self._parse_length(height, False, False, 1, 1000, height_cb)
def set_size(_pixbuf, w, h, dims):
"""
FIXME: Floats should be relative to the whole textview, and
resize with it. This needs new pifbufs for every resize,
@ -569,7 +578,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
if not dims[1]:
dims[1] = h
loader.set_size(*dims)
if w or h:
if width or height:
loader.connect('size-prepared', set_size, dims)
loader.write(mem)
loader.close()
@ -621,12 +631,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
attr = attr.lower()
val = val
try:
method = self.__style_methods[attr]
getattr(self, self.__style_methods[attr])(tag, val)
except KeyError:
log.warning('Style attribute "%s" requested '
'but not yet implemented', attr)
else:
method(self, tag, val)
self.styles.append(tag)
def _end_span(self):
@ -660,7 +668,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
else:
self._insert_text(text.strip('\n'))
def _anchor_event(self, tag, textview, event, iter_, href, type_):
def _anchor_event(self, _tag, _textview, event, _iter, href, type_):
if event.type == Gdk.EventType.BUTTON_PRESS:
self.textview.emit('url-clicked', href, type_)
return True
@ -668,8 +676,8 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
def handle_specials(self, text):
if self.conv_textview:
self.iter = self.conv_textview.detect_and_print_special_text(text,
self._get_style_tags(), iter_=self.iter)
self.iter = self.conv_textview.detect_and_print_special_text(
text, self._get_style_tags(), iter_=self.iter)
else:
self._insert_text(text)
@ -682,7 +690,6 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
self.text += content
self.starting = False
def startElement(self, name, attrs):
self._flush_text()
klass = [i for i in attrs.get('class', ' ').split(' ') if i]
@ -767,10 +774,10 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
log.warning('Unhandled element "%s"', name)
def endElement(self, name):
endPreserving = False
newLine = False
end_preserving = False
newline = False
if name == 'br':
newLine = True
newline = True
elif name == 'hr':
#FIXME: plenty of unused attributes (width, height,...) :)
self._jump_line()
@ -779,7 +786,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
elif name in LIST_ELEMS:
self.list_counters.pop()
elif name == 'li':
newLine = True
newline = True
elif name == 'img':
pass
elif name in ('body', 'html'):
@ -792,21 +799,20 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
pass
elif name in BLOCK:
if name == 'pre':
endPreserving = True
end_preserving = True
elif name in BLOCK_STRUCT:
newLine = True
newline = True
else:
log.warning("Unhandled element '%s'", name)
self._flush_text()
if endPreserving:
if end_preserving:
self.preserve = False
if newLine:
if newline:
self._jump_line()
self._end_span()
class HtmlTextView(Gtk.TextView):
def __init__(self, account=None):
Gtk.TextView.__init__(self)
self.set_wrap_mode(Gtk.WrapMode.CHAR)
@ -847,7 +853,7 @@ class HtmlTextView(Gtk.TextView):
self.tagSthAtSth.set_property('underline', Pango.Underline.SINGLE)
self.tagSthAtSth.connect('event', self.hyperlink_handler, 'sth_at_sth')
def __query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
def __query_tooltip(self, widget, x_pos, y_pos, _keyboard_mode, tooltip):
window = widget.get_window(Gtk.TextWindowType.TEXT)
x_pos, y_pos = self.window_to_buffer_coords(
Gtk.TextWindowType.TEXT, x_pos, y_pos)
@ -876,7 +882,7 @@ class HtmlTextView(Gtk.TextView):
if menu is None:
return
def destroy(menu, pspec):
def destroy(menu, _pspec):
visible = menu.get_property('visible')
if not visible:
GLib.idle_add(menu.destroy)
@ -942,8 +948,8 @@ class HtmlTextView(Gtk.TextView):
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % \
{'lat': lat, 'lon': lon}
helpers.launch_browser_mailer(kind, uri)
# other URIs
else:
# other URIs
helpers.launch_browser_mailer(kind, word)
def display_html(self, html, textview, conv_textview, iter_=None):
@ -952,17 +958,10 @@ class HtmlTextView(Gtk.TextView):
eob = iter_
else:
eob = buffer_.get_end_iter()
## this works too if libxml2 is not available
# parser = xml.sax.make_parser(['drv_libxml2'])
# parser.setFeature(xml.sax.handler.feature_validation, True)
parser = xml.sax.make_parser()
parser.setContentHandler(HtmlHandler(textview, conv_textview, eob))
parser.parse(StringIO(html))
# too much space after :)
#if not eob.starts_line():
# buffer_.insert(eob, '\n')
@staticmethod
def _on_copy_clipboard(textview):
clipboard = textview.get_clipboard(Gdk.SELECTION_CLIPBOARD)