Mercurial > libervia-desktop-kivy
changeset 58:7aa2ffff9067
chat: <img/> tag handling first draft:
We need to have several widgets to handle <img/> (label(s) + image(s)), which make sizing and positioning complicated.
To make things simpler, we use a simple trick when several widgets are present: we split the labels in as many labels as there are words, so we can take profit of the StackLayout.
The split is done after the XHTML is parsed, so after all the widgets are present, and is done only once. This means that label need to be reparsed to be splitted.
This is not perfect, but should be a reasonable solutions until we implement a real XHTML engine (probably CEF widget and Webview).
image sizing and alignment is not handled correcly now, should be fixed soon.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 28 Sep 2016 22:02:36 +0200 |
parents | a51ea7874e43 |
children | 2aa44a82d0e7 |
files | src/cagou/plugins/plugin_wid_chat.kv src/cagou/plugins/plugin_wid_chat.py |
diffstat | 2 files changed, 161 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/src/cagou/plugins/plugin_wid_chat.kv Sun Sep 25 16:06:56 2016 +0200 +++ b/src/cagou/plugins/plugin_wid_chat.kv Wed Sep 28 22:02:36 2016 +0200 @@ -15,6 +15,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +<SimpleXHTMLWidgetEscapedText>: + size_hint: None, None + size: self.texture_size + +<SimpleXHTMLWidgetText>: + size_hint: None, None + size: self.texture_size + +<SimpleXHTMLWidgetImage>: + size_hint: 1, None + <MessAvatar>: size_hint: None, None size: dp(30), dp(30) @@ -61,14 +72,15 @@ BorderImage: source: app.expand("{media}/misc/black.png") if root.mess_data.type == "info" else app.expand("{media}/misc/borders/{}.jpg", "blue" if root.mess_data.own_mess else "gray") pos: self.pos - size: self.size + size: self.content_width, self.height id: mess_xhtml + size_hint: 0.8, None + height: self.minimum_height xhtml: root.message_xhtml or self.escape(root.message or u' ') color: (0.74,0.74,0.24,1) if root.mess_data.type == "info" else (0, 0, 0, 1) padding: root.mess_padding bold: True if root.mess_data.type == "info" else False - <MessageInputWidget>: size_hint: 1, None height: dp(40)
--- a/src/cagou/plugins/plugin_wid_chat.py Sun Sep 25 16:06:56 2016 +0200 +++ b/src/cagou/plugins/plugin_wid_chat.py Wed Sep 28 22:02:36 2016 +0200 @@ -27,6 +27,7 @@ from kivy.uix.scrollview import ScrollView from kivy.uix.textinput import TextInput from kivy.uix.label import Label +from kivy.uix.image import AsyncImage from kivy.metrics import dp from kivy.utils import escape_markup from kivy import properties @@ -59,12 +60,24 @@ super(Escape, self).__init__(text) +class SimpleXHTMLWidgetEscapedText(Label): + pass + +class SimpleXHTMLWidgetText(Label): + pass + +class SimpleXHTMLWidgetImage(AsyncImage): + pass + class SimpleXHTMLWidget(StackLayout): """widget handling simple XHTML parsing""" xhtml = properties.StringProperty() color = properties.ListProperty([1, 1, 1, 1]) # XXX: bold is only used for escaped text bold = properties.BooleanProperty(False) + content_width = properties.NumericProperty(0) + + # text/XHTML input def on_xhtml(self, instance, xhtml): """parse xhtml and set content accordingly @@ -74,7 +87,7 @@ """ self.clear_widgets() if isinstance(xhtml, Escape): - label = Label(text=xhtml, color=self.color) + label = SimpleXHTMLWidgetEscapedText(text=xhtml, color=self.color) self.bind(color=label.setter('color')) self.bind(bold=label.setter('bold')) self.add_widget(label) @@ -88,6 +101,118 @@ """mark that a text need to be escaped (i.e. no markup)""" return Escape(text) + # sizing + + def on_width(self, instance, width): + if len(self.children) == 1: + wid = self.children[0] + if isinstance(wid, Label): + try: + full_width = wid._full_width + except AttributeError: + wid.size_hint = (None, None) + wid.texture_update() + full_width = wid._full_width = wid.texture_size[0] + if full_width > width: + wid.text_size = width, None + else: + wid.text_size = None, None + self.content_width = wid.width + self.padding[0] + self.padding[2] + else: + wid.size_hint(1, None) + wid.height = 100 + self.content_width = self.width + else: + self._do_complexe_sizing(width) + + def _do_complexe_sizing(self, width): + try: + self.splitted + except AttributeError: + # XXX: to make things easier, we split labels in words + log.debug(u"split start") + children = self.children[::-1] + self.clear_widgets() + for child in children: + if isinstance(child, Label): + log.debug(u"label before split: {}".format(child.text)) + styles = [] + tag = False + new_text = [] + current_tag = [] + current_value = [] + current_wid = self._createText() + value = False + close = False + # we will parse the text and create a new widget + # on each new word (actually each space) + # FIXME: handle '\n' and other white chars + for c in child.text: + if tag: + # we are parsing a markup tag + if c == u']': + current_tag_s = u''.join(current_tag) + current_style = (current_tag_s, u''.join(current_value)) + if close: + for idx, s in enumerate(reversed(styles)): + if s[0] == current_tag_s: + del styles[len(styles) - idx - 1] + break + else: + styles.append(current_style) + current_tag = [] + current_value = [] + tag = False + value = False + close = False + elif c == u'/': + close = True + elif c == u'=': + value = True + elif value: + current_value.append(c) + else: + current_tag.append(c) + new_text.append(c) + else: + # we are parsing regular text + if c == u'[': + new_text.append(c) + tag = True + elif c == u' ': + # new word, we do a new widget + new_text.append(u' ') + for t, v in reversed(styles): + new_text.append(u'[/{}]'.format(t)) + current_wid.text = u''.join(new_text) + new_text = [] + self.add_widget(current_wid) + log.debug(u"new widget: {}".format(current_wid.text)) + current_wid = self._createText() + for t, v in styles: + new_text.append(u'[{tag}{value}]'.format( + tag = t, + value = u'={}'.format(v) if v else u'')) + else: + new_text.append(c) + if current_wid.text: + # we may have a remaining widget after the parsing + close_styles = [] + for t, v in reversed(styles): + close_styles.append(u'[/{}]'.format(t)) + current_wid.text = u''.join(close_styles) + self.add_widget(current_wid) + log.debug(u"new widget: {}".format(current_wid.text)) + else: + # non Label widgets, we just add them + self.add_widget(child) + self.splitted = True + log.debug(u"split OK") + + # we now set the content width + # FIXME: for now we just use the full width + self.content_width = width + # XHTML parsing methods def _callParseMethod(self, e): @@ -113,13 +238,13 @@ self.styles is needed to keep track of styles to remove should most probably be set to True """ - if append_to_list: - self.styles.append((tag, value)) label = self._getLabel() label.text += u'[{tag}{value}]'.format( tag = tag, value = u'={}'.format(value) if value else '' ) + if append_to_list: + self.styles.append((tag, value)) def _removeStyle(self, tag, remove_from_list=True): """remove a markup style from the label @@ -151,14 +276,17 @@ current styles will be closed and reopened if needed """ self._closeLabel() - label = Label(color=self.color, markup=True) - self.current_wid = label - self.bind(color=self.current_wid.setter('color')) - label.bind(texture_size=label.setter('size')) + self.current_wid = self._createText() for tag, value in self.styles: self._addStyle(tag, value, append_to_list=False) self.add_widget(self.current_wid) + def _createText(self): + label = SimpleXHTMLWidgetText(color=self.color, markup=True) + self.bind(color=label.setter('color')) + label.bind(texture_size=label.setter('size')) + return label + def _closeLabel(self): """close current style tags in current label @@ -250,8 +378,19 @@ def xhtml_em(self, elem): self.xhtml_generic(elem, markup='i') + def xhtml_img(self, elem): + try: + src = elem.attrib['src'] + except KeyError: + log.warning(u"<img> element without src: {}".format(ET.tostring(elem))) + return + img = SimpleXHTMLWidgetImage(source=src) + self.current_wid = img + self.add_widget(img) + def xhtml_p(self, elem): - self._addLabel() + if isinstance(self.current_wid, Label): + self.current_wid.text+="\n\n" self.xhtml_generic(elem) def xhtml_span(self, elem):