HtmlTextView: Fix pylint errors and minor refactoring
This commit is contained in:
parent
97310a059a
commit
eedd0c2a72
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue