changeset 325:5868a5575e01

chat: cleaning + some improvments: - code cleaning, removed some dead code - some improvments on the way size is calculated, removed unnecessary sizing methods which were linked to properties - image have now a max size, this avoid gigantic image in the whole screen - in SimpleXHTMLWidget, Label are now splitted when xhtml is set - use a DelayedBoxLayout for messages, as they are really slow to be resized - use of RecycleView has been investigated, but it is not currently usable as dynamic contents are not propertly handled (see https://github.com/kivy/kivy/issues/6580 and https://github.com/kivy/kivy/issues/6582). Furthermore, some tests with RecycleView on Android don't give the expected speed boost, so BoxLayout still seems like the way to go for the moment. To be re-investigated at a later point if necessary.
author Goffi <goffi@goffi.org>
date Fri, 06 Dec 2019 13:25:31 +0100
parents 4374cb741eb5
children d9d2b56f46db
files cagou/core/constants.py cagou/core/simple_xhtml.py cagou/kv/simple_xhtml.kv cagou/plugins/plugin_wid_chat.kv cagou/plugins/plugin_wid_chat.py
diffstat 5 files changed, 218 insertions(+), 247 deletions(-) [+]
line wrap: on
line diff
--- a/cagou/core/constants.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/constants.py	Fri Dec 06 13:25:31 2019 +0100
@@ -52,3 +52,7 @@
     COLOR_ERROR = (1.0, 0.0, 0.0, 1)
 
     COLOR_BTN_LIGHT = (0.4, 0.4, 0.4, 1)
+
+    # values are in dp
+    IMG_MAX_WIDTH = 500
+    IMG_MAX_HEIGHT = 500
--- a/cagou/core/simple_xhtml.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/simple_xhtml.py	Fri Dec 06 13:25:31 2019 +0100
@@ -18,17 +18,20 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-from sat.core import log as logging
-log = logging.getLogger(__name__)
+import webbrowser
+from xml.etree import ElementTree as ET
 from kivy.uix.stacklayout import StackLayout
 from kivy.uix.label import Label
 from kivy.utils import escape_markup
-from kivy.metrics import sp
+from kivy.metrics import sp, dp
 from kivy import properties
-from xml.etree import ElementTree as ET
+from sat.core import log as logging
 from sat_frontends.tools import css_color, strings as sat_strings
 from cagou.core.image import AsyncImage
-import webbrowser
+from cagou.core.constants import Const as C
+
+
+log = logging.getLogger(__name__)
 
 
 class Escape(str):
@@ -51,10 +54,9 @@
                 text_elts.append(escape_markup(m.string[0:m.start()]))
                 link_key = 'link_' + str(links)
                 url = m.group()
-                text_elts.append('[color=5500ff][ref={link}]{url}[/ref][/color]'.format(
-                    link = link_key,
-                    url = url
-                    ))
+                escaped_url = escape_markup(url)
+                text_elts.append(
+                    f'[color=5500ff][ref={link_key}]{escaped_url}[/ref][/color]')
                 if not links:
                     self.ref_urls = {link_key: url}
                 else:
@@ -83,57 +85,55 @@
 class SimpleXHTMLWidgetText(Label):
 
     def on_parent(self, instance, parent):
-        self.font_size = parent.font_size
+        if parent is not None:
+            self.font_size = parent.font_size
 
 
 class SimpleXHTMLWidgetImage(AsyncImage):
     # following properties are desired height/width
     # i.e. the ones specified in height/width attributes of <img>
     # (or wanted for whatever reason)
-    # set to 0 to ignore them
-    target_height = properties.NumericProperty()
-    target_width = properties.NumericProperty()
-
-    def _get_parent_container(self):
-        """get parent SimpleXHTMLWidget instance
+    # set to None to ignore them
+    target_height = properties.NumericProperty(allownone=True)
+    target_width = properties.NumericProperty(allownone=True)
 
-        @param warning(bool): if True display a log.error if nothing found
-        @return (SimpleXHTMLWidget, None): found SimpleXHTMLWidget instance
-        """
-        parent = self.parent
-        while parent and not isinstance(parent, SimpleXHTMLWidget):
-            parent = parent.parent
-        if parent is None:
-            log.error("no SimpleXHTMLWidget parent found")
-        return parent
+    def __init__(self, **kwargs):
+        # best calculated size
+        self._best_width = self._best_height = 100
+        super().__init__(**kwargs)
 
-    def _on_source_load(self, value):
-        # this method is called when image is loaded
-        super(SimpleXHTMLWidgetImage, self)._on_source_load(value)
-        if self.parent is not None:
-            container = self._get_parent_container()
-            # image is loaded, we need to recalculate size
-            self.on_container_width(container, container.width)
-
-    def on_container_width(self, container, container_width):
-        """adapt size according to container width
+    def on_texture(self, instance, texture):
+        """Adapt the size according to max size and target_*"""
+        if texture is None:
+            return
+        max_width, max_height = dp(C.IMG_MAX_WIDTH), dp(C.IMG_MAX_HEIGHT)
+        width, height = texture.size
+        if self.target_width:
+            width = min(width, self.target_width)
+        if width > max_width:
+            width = C.IMG_MAX_WIDTH
 
-        called when parent container (SimpleXHTMLWidget) width change
-        """
-        target_size = (self.target_width or self.texture.width, self.target_height or self.texture.height)
-        padding = container.padding
-        padding_h = (padding[0] + padding[2]) if len(padding) == 4 else padding[0]
-        width = container_width - padding_h
-        if target_size[0] < width:
-            self.size = target_size
-        else:
-            height = width / self.image_ratio
-            self.size = (width, height)
+        height = width / self.image_ratio
+
+        if self.target_height:
+            height = min(height, self.target_height)
+
+        if height > max_height:
+            height = max_height
+            width = height * self.image_ratio
+
+        self.width, self.height = self._best_width, self._best_height = width, height
 
     def on_parent(self, instance, parent):
         if parent is not None:
-            container = self._get_parent_container()
-            container.bind(width=self.on_container_width)
+            parent.bind(width=self.on_parent_width)
+
+    def on_parent_width(self, instance, width):
+        if self._best_width > width:
+            self.width = width
+            self.height = width / self.image_ratio
+        else:
+            self.width, self.height = self._best_width, self._best_height
 
 
 class SimpleXHTMLWidget(StackLayout):
@@ -142,7 +142,6 @@
     color = properties.ListProperty([1, 1, 1, 1])
     # XXX: bold is only used for escaped text
     bold = properties.BooleanProperty(False)
-    content_width = properties.NumericProperty(0)
     font_size = properties.NumericProperty(sp(14))
 
     # text/XHTML input
@@ -156,151 +155,117 @@
         if isinstance(xhtml, Escape):
             label = SimpleXHTMLWidgetEscapedText(
                 text=xhtml, color=self.color, bold=self.bold)
+            self.bind(font_size=label.setter('font_size'))
             self.bind(color=label.setter('color'))
             self.bind(bold=label.setter('bold'))
             self.add_widget(label)
         else:
-            xhtml = ET.fromstring(xhtml.encode('utf-8'))
+            xhtml = ET.fromstring(xhtml.encode())
             self.current_wid = None
             self.styles = []
             self._callParseMethod(xhtml)
+        if len(self.children) > 1:
+            self._do_split_labels()
 
     def escape(self, text):
         """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):
-                # we have simple text
-                try:
-                    full_width = wid._full_width
-                except AttributeError:
-                    # on first time, we need the required size
-                    # for the full text, without width limit
-                    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
-                    wid.width = width
-                else:
-                    wid.text_size = None, None
-                    wid.texture_update()
-                    wid.width = wid.texture_size[0]
-                self.content_width = wid.width + self.padding[0] + self.padding[2]
+    def _do_split_labels(self):
+        """Split labels so their content can flow with images"""
+        # XXX: to make things easier, we split labels in words
+        log.debug("labels splitting start")
+        children = self.children[::-1]
+        self.clear_widgets()
+        for child in children:
+            if isinstance(child, Label):
+                log.debug("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 == ']':
+                            current_tag_s = ''.join(current_tag)
+                            current_style = (current_tag_s, ''.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 == '/':
+                            close = True
+                        elif c == '=':
+                            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 == '[':
+                            new_text.append(c)
+                            tag = True
+                        elif c == ' ':
+                            # new word, we do a new widget
+                            new_text.append(' ')
+                            for t, v in reversed(styles):
+                                new_text.append('[/{}]'.format(t))
+                            current_wid.text = ''.join(new_text)
+                            new_text = []
+                            self.add_widget(current_wid)
+                            log.debug("new widget: {}".format(current_wid.text))
+                            current_wid = self._createText()
+                            for t, v in styles:
+                                new_text.append('[{tag}{value}]'.format(
+                                    tag = t,
+                                    value = '={}'.format(v) if v else ''))
+                        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('[/{}]'.format(t))
+                    current_wid.text = ''.join(close_styles)
+                    self.add_widget(current_wid)
+                    log.debug("new widget: {}".format(current_wid.text))
             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("split start")
-            children = self.children[::-1]
-            self.clear_widgets()
-            for child in children:
-                if isinstance(child, Label):
-                    log.debug("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 == ']':
-                                current_tag_s = ''.join(current_tag)
-                                current_style = (current_tag_s, ''.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 == '/':
-                                close = True
-                            elif c == '=':
-                                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 == '[':
-                                new_text.append(c)
-                                tag = True
-                            elif c == ' ':
-                                # new word, we do a new widget
-                                new_text.append(' ')
-                                for t, v in reversed(styles):
-                                    new_text.append('[/{}]'.format(t))
-                                current_wid.text = ''.join(new_text)
-                                new_text = []
-                                self.add_widget(current_wid)
-                                log.debug("new widget: {}".format(current_wid.text))
-                                current_wid = self._createText()
-                                for t, v in styles:
-                                    new_text.append('[{tag}{value}]'.format(
-                                        tag = t,
-                                        value = '={}'.format(v) if v else ''))
-                            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('[/{}]'.format(t))
-                        current_wid.text = ''.join(close_styles)
-                        self.add_widget(current_wid)
-                        log.debug("new widget: {}".format(current_wid.text))
-                else:
-                    # non Label widgets, we just add them
-                    self.add_widget(child)
-            self.splitted = True
-            log.debug("split OK")
-
-        # we now set the content width
-        # FIXME: for now we just use the full width
-        self.content_width = width
+                # non Label widgets, we just add them
+                self.add_widget(child)
+        self.splitted = True
+        log.debug("split OK")
 
     # XHTML parsing methods
 
     def _callParseMethod(self, e):
-        """call the suitable method to parse the element
+        """Call the suitable method to parse the element
 
         self.xhtml_[tag] will be called if it exists, else
         self.xhtml_generic will be used
         @param e(ET.Element): element to parse
         """
         try:
-            method = getattr(self, "xhtml_{}".format(e.tag))
+            method = getattr(self, f"xhtml_{e.tag}")
         except AttributeError:
-            log.warning("Unhandled XHTML tag: {}".format(e.tag))
+            log.warning(f"Unhandled XHTML tag: {e.tag}")
             method = self.xhtml_generic
         method(e)
 
@@ -384,14 +349,14 @@
             try:
                 prop, value = style.split(':')
             except ValueError:
-                log.warning("can't parse style: {}".format(style))
+                log.warning(f"can't parse style: {style}")
                 continue
             prop = prop.strip().replace('-', '_')
             value = value.strip()
             try:
-                method = getattr(self, "css_{}".format(prop))
+                method = getattr(self, f"css_{prop}")
             except AttributeError:
-                log.warning("Unhandled CSS: {}".format(prop))
+                log.warning(f"Unhandled CSS: {prop}")
             else:
                 method(e, value)
         self._css_styles = self.styles[styles_limit:]
@@ -407,7 +372,7 @@
         del self._css_styles
 
     def xhtml_generic(self, elem, style=True, markup=None):
-        """generic method for adding HTML elements
+        """Generic method for adding HTML elements
 
         this method handle content, style and children parsing
         @param elem(ET.Element): element to add
@@ -462,13 +427,13 @@
         try:
             target_height = int(elem.get('height', 0))
         except ValueError:
-            log.warning("Can't parse image height: {}".format(elem.get('height')))
-            target_height = 0
+            log.warning(f"Can't parse image height: {elem.get('height')}")
+            target_height = None
         try:
             target_width = int(elem.get('width', 0))
         except ValueError:
-            log.warning("Can't parse image width: {}".format(elem.get('width')))
-            target_width = 0
+            log.warning(f"Can't parse image width: {elem.get('width')}")
+            target_width = None
 
         img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width)
         self.current_wid = img
--- a/cagou/kv/simple_xhtml.kv	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/kv/simple_xhtml.kv	Fri Dec 06 13:25:31 2019 +0100
@@ -14,10 +14,17 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#:import C cagou.core.constants.Const
+
+
+<SimpleXHTMLWidget>:
+    size_hint: 1, None
+    height: self.minimum_height
 
 <SimpleXHTMLWidgetEscapedText>:
-    size_hint: None, None
-    size: self.texture_size
+    size_hint: 1, None
+    text_size: self.width, None
+    height: self.texture_size[1]
 
 <SimpleXHTMLWidgetText>:
     size_hint: None, None
--- a/cagou/plugins/plugin_wid_chat.kv	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_chat.kv	Fri Dec 06 13:25:31 2019 +0100
@@ -14,10 +14,11 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#:import SimpleXHTMLWidget cagou.core.simple_xhtml.SimpleXHTMLWidget
 #:import _ sat.core.i18n._
 #:import C cagou.core.constants.Const
 #:import escape kivy.utils.escape_markup
+#:import SimpleXHTMLWidget cagou.core.simple_xhtml.SimpleXHTMLWidget
+#:import DelayedBoxLayout cagou.core.common_widgets.DelayedBoxLayout
 
 
 <MessAvatar>:
@@ -31,46 +32,38 @@
             pos: self.pos
             size: self.size
 
-<MessagesWidget>:
-    cols: 1
-    padding: dp(10)
-    spacing: dp(5)
-    size_hint: 1, None
-    height: self.minimum_height
-    canvas.before:
-        Color:
-            rgba: 1, 1, 1, 1
-        Rectangle:
-            pos: self.pos
-            size: self.size
 
 <MessageWidget>:
     size_hint: 1, None
-    height: right_part.minimum_height
-    on_width: self.widthAdjust()
     avatar: avatar
     delivery: delivery
     mess_xhtml: mess_xhtml
+    right_part: right_part
+    header_box: header_box
+    height: self.minimum_height
     BoxLayout:
         orientation: 'vertical'
         width: avatar.width
         size_hint: None, 1
         MessAvatar:
             id: avatar
+            source: (root.mess_data.avatar or '') if root.mess_data else ''
         Widget:
             # use to push the avatar on the top
             size_hint: 1, 1
     BoxLayout:
+        size_hint: 1, None
         orientation: 'vertical'
         id: right_part
+        height: header_box.height + mess_xhtml.height
         BoxLayout:
             id: header_box
             size_hint: 1, None
-            height: time_label.height if root.mess_data.type != C.MESS_TYPE_INFO else 0
-            opacity: 1 if root.mess_data.type != C.MESS_TYPE_INFO else 0
+            height: time_label.height if root.mess_type != C.MESS_TYPE_INFO else 0
+            opacity: 1 if root.mess_type != C.MESS_TYPE_INFO else 0
             Label:
                 id: time_label
-                color: (0, 0, 0, 1) if root.mess_data.own_mess else (0.55,0.55,0.55,1)
+                color: (0, 0, 0, 1) if root.own_mess else (0.55,0.55,0.55,1)
                 font_size: root.font_size
                 text_size: None, None
                 size_hint: None, None
@@ -78,7 +71,7 @@
                 padding: dp(5), 0
                 markup: True
                 valign: 'middle'
-                text: u"[b]{}[/b], {}".format(escape(root.mess_data.nick), root.mess_data.time_text)
+                text: u"[b]{}[/b], {}".format(escape(root.nick), root.time_text)
             Label:
                 id: delivery
                 color: C.COLOR_BTN_LIGHT
@@ -97,20 +90,26 @@
             size_hint: 1, 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)
+            color: (0.74,0.74,0.24,1) if root.mess_type == "info" else (0, 0, 0, 1)
             padding: root.mess_padding
-            bold: True if root.mess_data.type == "info" else False
+            bold: True if root.mess_type == "info" else False
+
 
 <Chat>:
+    message_input: message_input
     messages_widget: messages_widget
-    message_input: message_input
     ScrollView:
         scroll_y: 0
         do_scroll_x: False
         scroll_type: ['bars', 'content']
         bar_width: dp(6)
-        MessagesWidget:
+        DelayedBoxLayout:
             id: messages_widget
+            size_hint_y: None
+            padding: [app.MARGIN_LEFT, 0, app.MARGIN_RIGHT, dp(10)]
+            spacing: dp(10)
+            height: self.minimum_height
+            orientation: 'vertical'
     MessageInputBox:
         size_hint: 1, None
         height: self.minimum_height
--- a/cagou/plugins/plugin_wid_chat.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_chat.py	Fri Dec 06 13:25:31 2019 +0100
@@ -1,5 +1,4 @@
 #!/usr/bin/python
-# -*- coding: utf-8 -*-
 
 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client
 # Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
@@ -21,27 +20,27 @@
 from functools import partial
 import mimetypes
 import sys
-from sat.core import log as logging
-from sat.core.i18n import _
-from sat.core import exceptions
-from cagou.core.constants import Const as C
 from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.gridlayout import GridLayout
 from kivy.uix.textinput import TextInput
 from kivy.metrics import sp, dp
 from kivy.clock import Clock
 from kivy import properties
+from kivy.uix.dropdown import DropDown
+from kivy.core.window import Window
+from sat.core import log as logging
+from sat.core.i18n import _
+from sat.core import exceptions
 from sat_frontends.quick_frontend import quick_widgets
 from sat_frontends.quick_frontend import quick_chat
 from sat_frontends.tools import jid
+from cagou import G
+from cagou.core.constants import Const as C
 from cagou.core import cagou_widget
 from cagou.core import xmlui
 from cagou.core.image import Image
 from cagou.core.common import SymbolButton, JidButton
-from kivy.uix.dropdown import DropDown
-from kivy.core.window import Window
-from cagou import G
 from cagou.core import menu
+# from random import randrange
 
 log = logging.getLogger(__name__)
 
@@ -73,63 +72,64 @@
     pass
 
 
-class MessageWidget(BoxLayout, quick_chat.MessageWidget):
+class MessageWidget(quick_chat.MessageWidget, BoxLayout):
     mess_data = properties.ObjectProperty()
     mess_xhtml = properties.ObjectProperty()
     mess_padding = (dp(5), dp(5))
     avatar = properties.ObjectProperty()
     delivery = properties.ObjectProperty()
     font_size = properties.NumericProperty(sp(12))
+    right_part = properties.ObjectProperty()
+    header_box = properties.ObjectProperty()
 
-    def __init__(self, **kwargs):
-        # self must be registered in widgets before kv is parsed
-        kwargs['mess_data'].widgets.add(self)
-        super(MessageWidget, self).__init__(**kwargs)
-        avatar_path = self.mess_data.avatar
-        if avatar_path is not None:
-            self.avatar.source = avatar_path
+    def on_mess_data(self, wid, mess_data):
+        mess_data.widgets.add(self)
 
     @property
     def chat(self):
         """return parent Chat instance"""
         return self.mess_data.parent
 
+    def _get_from_mess_data(self, name, default):
+        if self.mess_data is None:
+            return default
+        return getattr(self.mess_data, name)
+
     def _get_message(self):
         """Return currently displayed message"""
+        if self.mess_data is None:
+            return ""
         return self.mess_data.main_message
 
     def _set_message(self, message):
+        if self.mess_data is None:
+            return False
         if message == self.mess_data.message.get(""):
             return False
         self.mess_data.message = {"": message}
         return True
 
-    message = properties.AliasProperty(_get_message, _set_message)
-
-    @property
-    def message_xhtml(self):
-        """Return currently displayed message"""
-        return self.mess_data.main_message_xhtml
+    message = properties.AliasProperty(
+        partial(_get_from_mess_data, name="main_message", default=""),
+        _set_message,
+        bind=['mess_data'],
+    )
+    message_xhtml = properties.AliasProperty(
+        partial(_get_from_mess_data, name="main_message_xhtml", default=""),
+        bind=['mess_data'])
+    mess_type = properties.AliasProperty(
+        partial(_get_from_mess_data, name="type", default=""), bind=['mess_data'])
+    own_mess = properties.AliasProperty(
+        partial(_get_from_mess_data, name="own_mess", default=False), bind=['mess_data'])
+    nick = properties.AliasProperty(
+        partial(_get_from_mess_data, name="nick", default=""), bind=['mess_data'])
+    time_text = properties.AliasProperty(
+        partial(_get_from_mess_data, name="time_text", default=""), bind=['mess_data'])
 
     @property
     def info_type(self):
         return self.mess_data.info_type
 
-    def widthAdjust(self):
-        """this widget grows up with its children"""
-        pass
-        # parent = self.mess_xhtml.parent
-        # padding_x = self.mess_padding[0]
-        # text_width, text_height = self.mess_xhtml.texture_size
-        # if text_width > parent.width:
-        #     self.mess_xhtml.text_size = (parent.width - padding_x, None)
-        #     self.text_max = text_width
-        # elif self.mess_xhtml.text_size[0] is not None and text_width  < parent.width - padding_x:
-        #     if text_width < self.text_max:
-        #         self.mess_xhtml.text_size = (None, None)
-        #     else:
-        #         self.mess_xhtml.text_size = (parent.width  - padding_x, None)
-
     def update(self, update_dict):
         if 'avatar' in update_dict:
             self.avatar.source = update_dict['avatar']
@@ -175,10 +175,6 @@
         self.dispatch('on_text_validate')
 
 
-class MessagesWidget(GridLayout):
-    pass
-
-
 class TransferButton(SymbolButton):
     chat = properties.ObjectProperty()
 
@@ -426,8 +422,8 @@
         self.extra_menu = ExtraMenu(chat=self)
         extra_btn = ExtraButton(chat=self)
         self.headerInputAddExtra(extra_btn)
-        self.header_input.hint_text = "{}".format(target)
-        self.postInit()
+        self.header_input.hint_text = target
+        Clock.schedule_once(lambda dt: self.postInit(), 0)
 
     def __str__(self):
         return "Chat({})".format(self.target)