Mercurial > libervia-desktop-kivy
annotate src/cagou/plugins/plugin_wid_chat.py @ 105:ce6ef88f2cff
chat: avatar improvments:
- avatar source is not set anymore in kv but on MessageWidget init, so None value can be handled correclty
- Message widget add itself to mess_data before parsing kv, so call to properties in kv can update the widget correctly (specially important for embedded bridge)
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 15 Jan 2017 18:02:53 +0100 |
parents | c601e3d40342 |
children | 9909ed7a7a20 |
rev | line source |
---|---|
22 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client | |
5 # Copyright (C) 2016 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
23 from sat.core.i18n import _ | |
24 from cagou.core.constants import Const as C | |
78 | 25 from kivy.uix.boxlayout import BoxLayout |
45 | 26 from kivy.uix.gridlayout import GridLayout |
57 | 27 from kivy.uix.stacklayout import StackLayout |
22 | 28 from kivy.uix.textinput import TextInput |
57 | 29 from kivy.uix.label import Label |
45 | 30 from kivy.metrics import dp |
57 | 31 from kivy.utils import escape_markup |
22 | 32 from kivy import properties |
33 from sat_frontends.quick_frontend import quick_widgets | |
34 from sat_frontends.quick_frontend import quick_chat | |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
35 from sat_frontends.tools import jid, css_color, strings as sat_strings |
22 | 36 from cagou.core import cagou_widget |
102
20251d926cc2
chat: fixed bad size_hint setting on XHTML
Goffi <goffi@goffi.org>
parents:
100
diff
changeset
|
37 from cagou.core.image import Image, AsyncImage |
22 | 38 from cagou import G |
57 | 39 from xml.etree import ElementTree as ET |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
40 import webbrowser |
22 | 41 |
42 | |
43 PLUGIN_INFO = { | |
44 "name": _(u"chat"), | |
45 "main": "Chat", | |
46 "description": _(u"instant messaging with one person or a group"), | |
90
9a6121722669
chat, contact_list, selector: use of new icons from muchoslava
Goffi <goffi@goffi.org>
parents:
88
diff
changeset
|
47 "icon_small": u"{media}/icons/muchoslava/png/chat_new_32.png", |
9a6121722669
chat, contact_list, selector: use of new icons from muchoslava
Goffi <goffi@goffi.org>
parents:
88
diff
changeset
|
48 "icon_medium": u"{media}/icons/muchoslava/png/chat_new_44.png" |
22 | 49 } |
50 | |
51 | |
44
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
52 class MessAvatar(Image): |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
53 pass |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
54 |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
55 |
57 | 56 class Escape(unicode): |
57 """Class used to mark that a message need to be escaped""" | |
58 | |
59 def __init__(self, text): | |
60 super(Escape, self).__init__(text) | |
61 | |
62 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
63 class SimpleXHTMLWidgetEscapedText(Label): |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
64 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
65 def _addUrlMarkup(self, text): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
66 text_elts = [] |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
67 idx = 0 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
68 links = 0 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
69 while True: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
70 m = sat_strings.RE_URL.search(text[idx:]) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
71 if m is not None: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
72 text_elts.append(escape_markup(m.string[0:m.start()])) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
73 link_key = u'link_' + unicode(links) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
74 url = m.group() |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
75 text_elts.append(u'[color=5500ff][ref={link}]{url}[/ref][/color]'.format( |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
76 link = link_key, |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
77 url = url |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
78 )) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
79 if not links: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
80 self.ref_urls = {link_key: url} |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
81 else: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
82 self.ref_urls[link_key] = url |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
83 links += 1 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
84 idx += m.end() |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
85 else: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
86 if links: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
87 text_elts.append(escape_markup(text[idx:])) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
88 self.markup = True |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
89 self.text = u''.join(text_elts) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
90 break |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
91 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
92 def on_text(self, instance, text): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
93 # do NOT call the method if self.markup is set |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
94 # this would result in infinite loop (because self.text |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
95 # is changed if an URL is found, and in this case markup too) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
96 if text and not self.markup: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
97 self._addUrlMarkup(text) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
98 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
99 def on_ref_press(self, ref): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
100 url = self.ref_urls[ref] |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
101 webbrowser.open(url) |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
102 |
59 | 103 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
104 class SimpleXHTMLWidgetText(Label): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
105 pass |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
106 |
59 | 107 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
108 class SimpleXHTMLWidgetImage(AsyncImage): |
59 | 109 # following properties are desired height/width |
110 # i.e. the ones specified in height/width attributes of <img> | |
111 # (or wanted for whatever reason) | |
112 # set to 0 to ignore them | |
113 target_height = properties.NumericProperty() | |
114 target_width = properties.NumericProperty() | |
115 | |
116 def _get_parent_container(self): | |
117 """get parent SimpleXHTMLWidget instance | |
118 | |
119 @param warning(bool): if True display a log.error if nothing found | |
120 @return (SimpleXHTMLWidget, None): found SimpleXHTMLWidget instance | |
121 """ | |
122 parent = self.parent | |
123 while parent and not isinstance(parent, SimpleXHTMLWidget): | |
124 parent = parent.parent | |
125 if parent is None: | |
126 log.error(u"no SimpleXHTMLWidget parent found") | |
127 return parent | |
128 | |
129 def _on_source_load(self, value): | |
130 # this method is called when image is loaded | |
131 super(SimpleXHTMLWidgetImage, self)._on_source_load(value) | |
132 if self.parent is not None: | |
133 container = self._get_parent_container() | |
134 # image is loaded, we need to recalculate size | |
135 self.on_container_width(container, container.width) | |
136 | |
137 def on_container_width(self, container, container_width): | |
138 """adapt size according to container width | |
139 | |
140 called when parent container (SimpleXHTMLWidget) width change | |
141 """ | |
142 target_size = (self.target_width or self.texture.width, self.target_height or self.texture.height) | |
143 padding = container.padding | |
144 padding_h = (padding[0] + padding[2]) if len(padding) == 4 else padding[0] | |
145 width = container_width - padding_h | |
146 if target_size[0] < width: | |
147 self.size = target_size | |
148 else: | |
149 height = width / self.image_ratio | |
150 self.size = (width, height) | |
151 | |
152 def on_parent(self, instance, parent): | |
153 if parent is not None: | |
154 container = self._get_parent_container() | |
155 container.bind(width=self.on_container_width) | |
156 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
157 |
57 | 158 class SimpleXHTMLWidget(StackLayout): |
159 """widget handling simple XHTML parsing""" | |
160 xhtml = properties.StringProperty() | |
161 color = properties.ListProperty([1, 1, 1, 1]) | |
162 # XXX: bold is only used for escaped text | |
163 bold = properties.BooleanProperty(False) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
164 content_width = properties.NumericProperty(0) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
165 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
166 # text/XHTML input |
57 | 167 |
168 def on_xhtml(self, instance, xhtml): | |
169 """parse xhtml and set content accordingly | |
170 | |
171 if xhtml is an instance of Escape, a Label with not markup | |
172 will be used | |
173 """ | |
174 self.clear_widgets() | |
175 if isinstance(xhtml, Escape): | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
176 label = SimpleXHTMLWidgetEscapedText(text=xhtml, color=self.color) |
57 | 177 self.bind(color=label.setter('color')) |
178 self.bind(bold=label.setter('bold')) | |
179 self.add_widget(label) | |
180 else: | |
181 xhtml = ET.fromstring(xhtml.encode('utf-8')) | |
182 self.current_wid = None | |
183 self.styles = [] | |
184 self._callParseMethod(xhtml) | |
185 | |
186 def escape(self, text): | |
187 """mark that a text need to be escaped (i.e. no markup)""" | |
188 return Escape(text) | |
189 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
190 # sizing |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
191 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
192 def on_width(self, instance, width): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
193 if len(self.children) == 1: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
194 wid = self.children[0] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
195 if isinstance(wid, Label): |
68 | 196 # we have simple text |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
197 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
198 full_width = wid._full_width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
199 except AttributeError: |
68 | 200 # on first time, we need the required size |
201 # for the full text, without width limit | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
202 wid.size_hint = (None, None) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
203 wid.texture_update() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
204 full_width = wid._full_width = wid.texture_size[0] |
68 | 205 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
206 if full_width > width: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
207 wid.text_size = width, None |
68 | 208 wid.width = width |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
209 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
210 wid.text_size = None, None |
68 | 211 wid.texture_update() |
212 wid.width = wid.texture_size[0] | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
213 self.content_width = wid.width + self.padding[0] + self.padding[2] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
214 else: |
102
20251d926cc2
chat: fixed bad size_hint setting on XHTML
Goffi <goffi@goffi.org>
parents:
100
diff
changeset
|
215 wid.size_hint = (1, None) |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
216 wid.height = 100 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
217 self.content_width = self.width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
218 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
219 self._do_complexe_sizing(width) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
220 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
221 def _do_complexe_sizing(self, width): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
222 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
223 self.splitted |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
224 except AttributeError: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
225 # XXX: to make things easier, we split labels in words |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
226 log.debug(u"split start") |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
227 children = self.children[::-1] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
228 self.clear_widgets() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
229 for child in children: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
230 if isinstance(child, Label): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
231 log.debug(u"label before split: {}".format(child.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
232 styles = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
233 tag = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
234 new_text = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
235 current_tag = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
236 current_value = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
237 current_wid = self._createText() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
238 value = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
239 close = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
240 # we will parse the text and create a new widget |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
241 # on each new word (actually each space) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
242 # FIXME: handle '\n' and other white chars |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
243 for c in child.text: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
244 if tag: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
245 # we are parsing a markup tag |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
246 if c == u']': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
247 current_tag_s = u''.join(current_tag) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
248 current_style = (current_tag_s, u''.join(current_value)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
249 if close: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
250 for idx, s in enumerate(reversed(styles)): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
251 if s[0] == current_tag_s: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
252 del styles[len(styles) - idx - 1] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
253 break |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
254 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
255 styles.append(current_style) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
256 current_tag = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
257 current_value = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
258 tag = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
259 value = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
260 close = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
261 elif c == u'/': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
262 close = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
263 elif c == u'=': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
264 value = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
265 elif value: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
266 current_value.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
267 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
268 current_tag.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
269 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
270 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
271 # we are parsing regular text |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
272 if c == u'[': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
273 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
274 tag = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
275 elif c == u' ': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
276 # new word, we do a new widget |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
277 new_text.append(u' ') |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
278 for t, v in reversed(styles): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
279 new_text.append(u'[/{}]'.format(t)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
280 current_wid.text = u''.join(new_text) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
281 new_text = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
282 self.add_widget(current_wid) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
283 log.debug(u"new widget: {}".format(current_wid.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
284 current_wid = self._createText() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
285 for t, v in styles: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
286 new_text.append(u'[{tag}{value}]'.format( |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
287 tag = t, |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
288 value = u'={}'.format(v) if v else u'')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
289 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
290 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
291 if current_wid.text: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
292 # we may have a remaining widget after the parsing |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
293 close_styles = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
294 for t, v in reversed(styles): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
295 close_styles.append(u'[/{}]'.format(t)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
296 current_wid.text = u''.join(close_styles) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
297 self.add_widget(current_wid) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
298 log.debug(u"new widget: {}".format(current_wid.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
299 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
300 # non Label widgets, we just add them |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
301 self.add_widget(child) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
302 self.splitted = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
303 log.debug(u"split OK") |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
304 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
305 # we now set the content width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
306 # FIXME: for now we just use the full width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
307 self.content_width = width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
308 |
57 | 309 # XHTML parsing methods |
310 | |
311 def _callParseMethod(self, e): | |
312 """call the suitable method to parse the element | |
313 | |
314 self.xhtml_[tag] will be called if it exists, else | |
315 self.xhtml_generic will be used | |
316 @param e(ET.Element): element to parse | |
317 """ | |
318 try: | |
319 method = getattr(self, "xhtml_{}".format(e.tag)) | |
320 except AttributeError: | |
321 log.warning(u"Unhandled XHTML tag: {}".format(e.tag)) | |
322 method = self.xhtml_generic | |
323 method(e) | |
324 | |
325 def _addStyle(self, tag, value=None, append_to_list=True): | |
326 """add a markup style to label | |
327 | |
328 @param tag(unicode): markup tag | |
329 @param value(unicode): markup value if suitable | |
330 @param append_to_list(bool): if True style we be added to self.styles | |
331 self.styles is needed to keep track of styles to remove | |
332 should most probably be set to True | |
333 """ | |
334 label = self._getLabel() | |
335 label.text += u'[{tag}{value}]'.format( | |
336 tag = tag, | |
337 value = u'={}'.format(value) if value else '' | |
338 ) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
339 if append_to_list: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
340 self.styles.append((tag, value)) |
57 | 341 |
342 def _removeStyle(self, tag, remove_from_list=True): | |
343 """remove a markup style from the label | |
344 | |
345 @param tag(unicode): markup tag to remove | |
346 @param remove_from_list(bool): if True, remove from self.styles too | |
347 should most probably be set to True | |
348 """ | |
349 label = self._getLabel() | |
350 label.text += u'[/{tag}]'.format( | |
351 tag = tag | |
352 ) | |
353 if remove_from_list: | |
354 for rev_idx, style in enumerate(reversed(self.styles)): | |
355 if style[0] == tag: | |
356 tag_idx = len(self.styles) - 1 - rev_idx | |
357 del self.styles[tag_idx] | |
358 break | |
359 | |
360 def _getLabel(self): | |
361 """get current Label if it exists, or create a new one""" | |
362 if not isinstance(self.current_wid, Label): | |
363 self._addLabel() | |
364 return self.current_wid | |
365 | |
366 def _addLabel(self): | |
367 """add a new Label | |
368 | |
369 current styles will be closed and reopened if needed | |
370 """ | |
371 self._closeLabel() | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
372 self.current_wid = self._createText() |
57 | 373 for tag, value in self.styles: |
374 self._addStyle(tag, value, append_to_list=False) | |
375 self.add_widget(self.current_wid) | |
376 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
377 def _createText(self): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
378 label = SimpleXHTMLWidgetText(color=self.color, markup=True) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
379 self.bind(color=label.setter('color')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
380 label.bind(texture_size=label.setter('size')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
381 return label |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
382 |
57 | 383 def _closeLabel(self): |
384 """close current style tags in current label | |
385 | |
386 needed when you change label to keep style between | |
387 different widgets | |
388 """ | |
389 if isinstance(self.current_wid, Label): | |
390 for tag, value in reversed(self.styles): | |
391 self._removeStyle(tag, remove_from_list=False) | |
392 | |
393 def _parseCSS(self, e): | |
394 """parse CSS found in "style" attribute of element | |
395 | |
396 self._css_styles will be created and contained markup styles added by this method | |
397 @param e(ET.Element): element which may have a "style" attribute | |
398 """ | |
399 styles_limit = len(self.styles) | |
400 styles = e.attrib['style'].split(u';') | |
401 for style in styles: | |
402 try: | |
403 prop, value = style.split(u':') | |
404 except ValueError: | |
405 log.warning(u"can't parse style: {}".format(style)) | |
406 continue | |
407 prop = prop.strip().replace(u'-', '_') | |
408 value = value.strip() | |
409 try: | |
410 method = getattr(self, "css_{}".format(prop)) | |
411 except AttributeError: | |
412 log.warning(u"Unhandled CSS: {}".format(prop)) | |
413 else: | |
414 method(e, value) | |
415 self._css_styles = self.styles[styles_limit:] | |
416 | |
417 def _closeCSS(self): | |
418 """removed CSS styles | |
419 | |
420 styles in self._css_styles will be removed | |
421 and the attribute will be deleted | |
422 """ | |
423 for tag, dummy in reversed(self._css_styles): | |
424 self._removeStyle(tag) | |
425 del self._css_styles | |
426 | |
427 def xhtml_generic(self, elem, style=True, markup=None): | |
428 """generic method for adding HTML elements | |
429 | |
430 this method handle content, style and children parsing | |
431 @param elem(ET.Element): element to add | |
432 @param style(bool): if True handle style attribute (CSS) | |
433 @param markup(tuple[unicode, (unicode, None)], None): kivy markup to use | |
434 """ | |
435 # we first add markup and CSS style | |
436 if markup is not None: | |
437 if isinstance(markup, basestring): | |
438 tag, value = markup, None | |
439 else: | |
440 tag, value = markup | |
441 self._addStyle(tag, value) | |
442 style_ = 'style' in elem.attrib and style | |
443 if style_: | |
444 self._parseCSS(elem) | |
445 | |
446 # then content | |
447 if elem.text: | |
448 self._getLabel().text += escape_markup(elem.text) | |
449 | |
450 # we parse the children | |
451 for child in elem: | |
452 self._callParseMethod(child) | |
453 | |
454 # closing CSS style and markup | |
455 if style_: | |
456 self._closeCSS() | |
457 if markup is not None: | |
458 self._removeStyle(tag) | |
459 | |
460 # and the tail, which is regular text | |
461 if elem.tail: | |
462 self._getLabel().text += escape_markup(elem.tail) | |
463 | |
464 # method handling XHTML elements | |
465 | |
466 def xhtml_br(self, elem): | |
467 label = self._getLabel() | |
468 label.text+='\n' | |
469 self.xhtml_generic(style=False) | |
470 | |
471 def xhtml_em(self, elem): | |
472 self.xhtml_generic(elem, markup='i') | |
473 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
474 def xhtml_img(self, elem): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
475 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
476 src = elem.attrib['src'] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
477 except KeyError: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
478 log.warning(u"<img> element without src: {}".format(ET.tostring(elem))) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
479 return |
59 | 480 try: |
481 target_height = int(elem.get(u'height', 0)) | |
482 except ValueError: | |
483 log.warning(u"Can't parse image height: {}".format(elem.get(u'height'))) | |
484 target_height = 0 | |
485 try: | |
486 target_width = int(elem.get(u'width', 0)) | |
487 except ValueError: | |
488 log.warning(u"Can't parse image width: {}".format(elem.get(u'width'))) | |
489 target_width = 0 | |
490 | |
491 img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
492 self.current_wid = img |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
493 self.add_widget(img) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
494 |
57 | 495 def xhtml_p(self, elem): |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
496 if isinstance(self.current_wid, Label): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
497 self.current_wid.text+="\n\n" |
57 | 498 self.xhtml_generic(elem) |
499 | |
500 def xhtml_span(self, elem): | |
501 self.xhtml_generic(elem) | |
502 | |
503 def xhtml_strong(self, elem): | |
504 self.xhtml_generic(elem, markup='b') | |
505 | |
506 # methods handling CSS properties | |
507 | |
508 def css_color(self, elem, value): | |
509 self._addStyle(u"color", css_color.parse(value)) | |
510 | |
511 def css_text_decoration(self, elem, value): | |
512 if value == u'underline': | |
513 log.warning(u"{} not handled yet, it needs Kivy 1.9.2 to be released".format(value)) | |
514 # FIXME: activate when 1.9.2 is out | |
515 # self._addStyle('u') | |
516 elif value == u'line-through': | |
517 log.warning(u"{} not handled yet, it needs Kivy 1.9.2 to be released".format(value)) | |
518 # FIXME: activate when 1.9.2 is out | |
519 # self._addStyle('s') | |
520 else: | |
521 log.warning(u"unhandled text decoration: {}".format(value)) | |
522 | |
523 | |
45 | 524 class MessageWidget(GridLayout): |
22 | 525 mess_data = properties.ObjectProperty() |
57 | 526 mess_xhtml = properties.ObjectProperty() |
45 | 527 mess_padding = (dp(5), dp(5)) |
47
abb81efef3bb
chat: update avatar following quick frontend improvments
Goffi <goffi@goffi.org>
parents:
46
diff
changeset
|
528 avatar = properties.ObjectProperty() |
103
c601e3d40342
chat: display delivery receipt (with a green check mark)
Goffi <goffi@goffi.org>
parents:
102
diff
changeset
|
529 delivery = properties.ObjectProperty() |
47
abb81efef3bb
chat: update avatar following quick frontend improvments
Goffi <goffi@goffi.org>
parents:
46
diff
changeset
|
530 |
abb81efef3bb
chat: update avatar following quick frontend improvments
Goffi <goffi@goffi.org>
parents:
46
diff
changeset
|
531 def __init__(self, **kwargs): |
105 | 532 # self must be registered in widgets before kv is parsed |
533 kwargs['mess_data'].widgets.add(self) | |
47
abb81efef3bb
chat: update avatar following quick frontend improvments
Goffi <goffi@goffi.org>
parents:
46
diff
changeset
|
534 super(MessageWidget, self).__init__(**kwargs) |
105 | 535 avatar_path = self.mess_data.avatar |
536 if avatar_path is not None: | |
537 self.avatar.source = avatar_path | |
44
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
538 |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
539 @property |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
540 def chat(self): |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
541 """return parent Chat instance""" |
7819e9efa250
chat: avatar and nick are now displayed, need further aesthetic improvments
Goffi <goffi@goffi.org>
parents:
42
diff
changeset
|
542 return self.mess_data.parent |
22 | 543 |
544 @property | |
545 def message(self): | |
546 """Return currently displayed message""" | |
547 return self.mess_data.main_message | |
548 | |
57 | 549 @property |
550 def message_xhtml(self): | |
551 """Return currently displayed message""" | |
552 return self.mess_data.main_message_xhtml | |
553 | |
45 | 554 def widthAdjust(self): |
22 | 555 """this widget grows up with its children""" |
57 | 556 pass |
557 # parent = self.mess_xhtml.parent | |
558 # padding_x = self.mess_padding[0] | |
559 # text_width, text_height = self.mess_xhtml.texture_size | |
560 # if text_width > parent.width: | |
561 # self.mess_xhtml.text_size = (parent.width - padding_x, None) | |
562 # self.text_max = text_width | |
563 # elif self.mess_xhtml.text_size[0] is not None and text_width < parent.width - padding_x: | |
564 # if text_width < self.text_max: | |
565 # self.mess_xhtml.text_size = (None, None) | |
566 # else: | |
567 # self.mess_xhtml.text_size = (parent.width - padding_x, None) | |
22 | 568 |
54
514c187afebc
chat: changed udpate to use dict instead of single key/value
Goffi <goffi@goffi.org>
parents:
47
diff
changeset
|
569 def update(self, update_dict): |
514c187afebc
chat: changed udpate to use dict instead of single key/value
Goffi <goffi@goffi.org>
parents:
47
diff
changeset
|
570 if 'avatar' in update_dict: |
514c187afebc
chat: changed udpate to use dict instead of single key/value
Goffi <goffi@goffi.org>
parents:
47
diff
changeset
|
571 self.avatar.source = update_dict['avatar'] |
103
c601e3d40342
chat: display delivery receipt (with a green check mark)
Goffi <goffi@goffi.org>
parents:
102
diff
changeset
|
572 if 'status' in update_dict: |
c601e3d40342
chat: display delivery receipt (with a green check mark)
Goffi <goffi@goffi.org>
parents:
102
diff
changeset
|
573 status = update_dict['status'] |
c601e3d40342
chat: display delivery receipt (with a green check mark)
Goffi <goffi@goffi.org>
parents:
102
diff
changeset
|
574 self.delivery.text = u'\u2714' if status == 'delivered' else u'' |
47
abb81efef3bb
chat: update avatar following quick frontend improvments
Goffi <goffi@goffi.org>
parents:
46
diff
changeset
|
575 |
22 | 576 |
78 | 577 class MessageInputBox(BoxLayout): |
578 pass | |
579 | |
580 | |
22 | 581 class MessageInputWidget(TextInput): |
582 | |
583 def _key_down(self, key, repeat=False): | |
584 displayed_str, internal_str, internal_action, scale = key | |
585 if internal_action == 'enter': | |
586 self.dispatch('on_text_validate') | |
587 else: | |
588 super(MessageInputWidget, self)._key_down(key, repeat) | |
589 | |
590 | |
45 | 591 class MessagesWidget(GridLayout): |
592 pass | |
22 | 593 |
594 | |
595 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget): | |
78 | 596 message_input = properties.ObjectProperty() |
86 | 597 messages_widget = properties.ObjectProperty() |
22 | 598 |
46
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
599 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None): |
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
600 quick_chat.QuickChat.__init__(self, host, target, type_, nick, occupants, subject, profiles=profiles) |
22 | 601 cagou_widget.CagouWidget.__init__(self) |
67 | 602 self.header_input.hint_text = u"{}".format(target) |
78 | 603 self.host.addListener('progressError', self.onProgressError, profiles) |
604 self.host.addListener('progressFinished', self.onProgressFinished, profiles) | |
605 self._waiting_pids = {} # waiting progress ids | |
22 | 606 self.postInit() |
607 | |
608 @classmethod | |
609 def factory(cls, plugin_info, target, profiles): | |
610 profiles = list(profiles) | |
611 if len(profiles) > 1: | |
612 raise NotImplementedError(u"Multi-profiles is not available yet for chat") | |
613 if target is None: | |
614 target = G.host.profiles[profiles[0]].whoami | |
615 return G.host.widgets.getOrCreateWidget(cls, target, on_new_widget=None, on_existing_widget=C.WIDGET_RECREATE, profiles=profiles) | |
616 | |
617 def messageDataConverter(self, idx, mess_id): | |
618 return {"mess_data": self.messages[mess_id]} | |
619 | |
620 def _onHistoryPrinted(self): | |
621 """Refresh or scroll down the focus after the history is printed""" | |
622 # self.adapter.data = self.messages | |
623 for mess_data in self.messages.itervalues(): | |
624 self.appendMessage(mess_data) | |
625 super(Chat, self)._onHistoryPrinted() | |
626 | |
627 def createMessage(self, message): | |
628 self.appendMessage(message) | |
629 | |
630 def appendMessage(self, mess_data): | |
631 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data)) | |
632 | |
633 def onSend(self, input_widget): | |
634 G.host.messageSend( | |
635 self.target, | |
636 {'': input_widget.text}, # TODO: handle language | |
637 mess_type = C.MESS_TYPE_GROUPCHAT if self.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT, # TODO: put this in QuickChat | |
638 profile_key=self.profile | |
639 ) | |
640 input_widget.text = '' | |
641 | |
78 | 642 def onProgressFinished(self, progress_id, metadata, profile): |
643 try: | |
88
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
644 callback, cleaning_cb = self._waiting_pids.pop(progress_id) |
78 | 645 except KeyError: |
646 return | |
88
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
647 if cleaning_cb is not None: |
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
648 cleaning_cb() |
78 | 649 callback(metadata, profile) |
650 | |
651 def onProgressError(self, progress_id, err_msg, profile): | |
652 try: | |
88
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
653 dummy, cleaning_cb = self._waiting_pids[progress_id] |
78 | 654 except KeyError: |
655 return | |
88
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
656 else: |
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
657 del self._waiting_pids[progress_id] |
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
658 if cleaning_cb is not None: |
3dc526bb4a5a
upload: plugin android gallery, first draft:
Goffi <goffi@goffi.org>
parents:
86
diff
changeset
|
659 cleaning_cb() |
78 | 660 # TODO: display message to user |
97
5d2289127bb7
menu (upload): better menu using dedicated widget:
Goffi <goffi@goffi.org>
parents:
90
diff
changeset
|
661 log.warning(u"Can't transfer file: {}".format(err_msg)) |
78 | 662 |
97
5d2289127bb7
menu (upload): better menu using dedicated widget:
Goffi <goffi@goffi.org>
parents:
90
diff
changeset
|
663 def fileTransferDone(self, metadata, profile): |
5d2289127bb7
menu (upload): better menu using dedicated widget:
Goffi <goffi@goffi.org>
parents:
90
diff
changeset
|
664 log.debug("file transfered: {}".format(metadata)) |
78 | 665 G.host.messageSend( |
666 self.target, | |
667 {'': metadata['url']}, | |
668 mess_type = C.MESS_TYPE_GROUPCHAT if self.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT, | |
669 profile_key=profile | |
670 ) | |
671 | |
97
5d2289127bb7
menu (upload): better menu using dedicated widget:
Goffi <goffi@goffi.org>
parents:
90
diff
changeset
|
672 def fileTransferCb(self, progress_data, cleaning_cb): |
78 | 673 try: |
674 progress_id = progress_data['progress'] | |
675 except KeyError: | |
676 xmlui = progress_data['xmlui'] | |
677 G.host.showUI(xmlui) | |
678 else: | |
97
5d2289127bb7
menu (upload): better menu using dedicated widget:
Goffi <goffi@goffi.org>
parents:
90
diff
changeset
|
679 self._waiting_pids[progress_id] = (self.fileTransferDone, cleaning_cb) |
78 | 680 |
98
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
681 def onTransferOK(self, file_path, cleaning_cb, transfer_type): |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
682 if transfer_type == C.TRANSFER_UPLOAD: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
683 G.host.bridge.fileUpload( |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
684 file_path, |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
685 "", |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
686 "", |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
687 {"ignore_tls_errors": C.BOOL_TRUE}, # FIXME: should not be the default |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
688 self.profile, |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
689 callback = lambda progress_data: self.fileTransferCb(progress_data, cleaning_cb) |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
690 ) |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
691 elif transfer_type == C.TRANSFER_SEND: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
692 if self.type == C.CHAT_GROUP: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
693 log.warning(u"P2P transfer is not possible for group chat") |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
694 # TODO: show an error dialog to user, or better hide the send button for MUC |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
695 else: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
696 jid_ = self.target |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
697 if not jid_.resource: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
698 jid_ = G.host.contact_lists[self.profile].getFullJid(jid_) |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
699 G.host.bridge.fileSend(jid_, file_path, "", "", profile=self.profile) |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
700 # TODO: notification of sending/failing |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
701 else: |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
702 raise log.error(u"transfer of type {} are not handled".format(transfer_type)) |
4d8c122b86a6
menu (upload): send transfer (i.e. P2P transfer) is now working
Goffi <goffi@goffi.org>
parents:
97
diff
changeset
|
703 |
78 | 704 |
46
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
705 def _mucJoinCb(self, joined_data): |
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
706 joined, room_jid_s, occupants, user_nick, subject, profile = joined_data |
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
707 self.host.mucRoomJoinedHandler(*joined_data[1:]) |
d6a63942d5ad
chat: fixed MUC joining following changes in backend
Goffi <goffi@goffi.org>
parents:
45
diff
changeset
|
708 jid_ = jid.JID(room_jid_s) |
42
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
709 self.changeWidget(jid_) |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
710 |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
711 def _mucJoinEb(self, failure): |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
712 log.warning(u"Can't join room: {}".format(failure)) |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
713 |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
714 def changeWidget(self, jid_): |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
715 """change current widget for a new one with given jid |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
716 |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
717 @param jid_(jid.JID): jid of the widget to create |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
718 """ |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
719 plugin_info = G.host.getPluginInfo(main=Chat) |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
720 factory = plugin_info['factory'] |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
721 G.host.switchWidget(self, factory(plugin_info, jid_, profiles=[self.profile])) |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
722 self.header_input.text = '' |
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
723 |
22 | 724 def onHeaderInput(self): |
725 text = self.header_input.text.strip() | |
726 try: | |
727 if text.count(u'@') != 1 or text.count(u' '): | |
728 raise ValueError | |
729 jid_ = jid.JID(text) | |
730 except ValueError: | |
731 log.info(u"entered text is not a jid") | |
732 return | |
733 | |
734 def discoCb(disco): | |
735 # TODO: check if plugin XEP-0045 is activated | |
736 if "conference" in [i[0] for i in disco[1]]: | |
42
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
737 G.host.bridge.mucJoin(unicode(jid_), "", "", self.profile, callback=self._mucJoinCb, errback=self._mucJoinEb) |
22 | 738 else: |
42
286865bc013a
chat: joining MUC using header input is now working:
Goffi <goffi@goffi.org>
parents:
41
diff
changeset
|
739 self.changeWidget(jid_) |
22 | 740 |
741 def discoEb(failure): | |
742 log.warning(u"Disco failure, ignore this text: {}".format(failure)) | |
743 | |
744 G.host.bridge.discoInfos(jid_.domain, self.profile, callback=discoCb, errback=discoEb) | |
745 | |
78 | 746 def _onDelete(self): |
747 self.host.removeListener('progressFinished', self.onProgressFinished) | |
748 self.host.removeListener('progressError', self.onProgressError) | |
749 return super(Chat, self).onDelete() | |
750 | |
37
6cf08d0ee460
chat: forbid scrolling on X axis + don't delete widget until explicitly requested (with force attribute)
Goffi <goffi@goffi.org>
parents:
35
diff
changeset
|
751 def onDelete(self, force=False): |
6cf08d0ee460
chat: forbid scrolling on X axis + don't delete widget until explicitly requested (with force attribute)
Goffi <goffi@goffi.org>
parents:
35
diff
changeset
|
752 if force==True: |
78 | 753 return self._onDelete() |
37
6cf08d0ee460
chat: forbid scrolling on X axis + don't delete widget until explicitly requested (with force attribute)
Goffi <goffi@goffi.org>
parents:
35
diff
changeset
|
754 if len(list(G.host.widgets.getWidgets(self.__class__, self.target, profiles=self.profiles))) > 1: |
6cf08d0ee460
chat: forbid scrolling on X axis + don't delete widget until explicitly requested (with force attribute)
Goffi <goffi@goffi.org>
parents:
35
diff
changeset
|
755 # we don't keep duplicate widgets |
78 | 756 return self._onDelete() |
37
6cf08d0ee460
chat: forbid scrolling on X axis + don't delete widget until explicitly requested (with force attribute)
Goffi <goffi@goffi.org>
parents:
35
diff
changeset
|
757 return False |
22 | 758 |
759 | |
760 PLUGIN_INFO["factory"] = Chat.factory | |
761 quick_widgets.register(quick_chat.QuickChat, Chat) |