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 |
|
25 from kivy.uix.boxlayout import BoxLayout |
|
26 from kivy.uix.scrollview import ScrollView |
|
27 from kivy.uix.textinput import TextInput |
|
28 from kivy import properties |
|
29 from sat_frontends.quick_frontend import quick_widgets |
|
30 from sat_frontends.quick_frontend import quick_chat |
|
31 from sat_frontends.tools import jid |
|
32 from cagou.core import cagou_widget |
|
33 from cagou import G |
|
34 |
|
35 |
|
36 PLUGIN_INFO = { |
|
37 "name": _(u"chat"), |
|
38 "main": "Chat", |
|
39 "description": _(u"instant messaging with one person or a group"), |
|
40 } |
|
41 |
|
42 |
|
43 class MessageWidget(BoxLayout): |
|
44 mess_data = properties.ObjectProperty() |
|
45 mess_label = properties.ObjectProperty(None) |
|
46 |
|
47 def __init__(self, **kwargs): |
|
48 BoxLayout.__init__(self, orientation='vertical', **kwargs) |
|
49 |
|
50 @property |
|
51 def message(self): |
|
52 """Return currently displayed message""" |
|
53 return self.mess_data.main_message |
|
54 |
|
55 def adjustMax(self, texture_size): |
|
56 """this widget grows up with its children""" |
|
57 width, height = texture_size |
|
58 if width > self.parent.width: |
|
59 self.mess_label.text_size = (self.parent.width - 10, None) |
|
60 |
|
61 |
|
62 class MessageInputWidget(TextInput): |
|
63 |
|
64 def _key_down(self, key, repeat=False): |
|
65 displayed_str, internal_str, internal_action, scale = key |
|
66 if internal_action == 'enter': |
|
67 self.dispatch('on_text_validate') |
|
68 else: |
|
69 super(MessageInputWidget, self)._key_down(key, repeat) |
|
70 |
|
71 |
|
72 class MessagesWidget(BoxLayout): |
|
73 _spacing = properties.NumericProperty(10) |
|
74 _padding = properties.NumericProperty(5) |
|
75 |
|
76 def __init__(self, **kwargs): |
|
77 kwargs['orientation'] = 'vertical' |
|
78 kwargs['size_hint'] = (1, None) |
|
79 super(MessagesWidget, self).__init__(**kwargs) |
|
80 |
|
81 def sizeAdjust(self): |
|
82 self.height = sum([(c.height+self._padding*2) for c in self.children]) + self._spacing |
|
83 |
|
84 |
|
85 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget): |
|
86 |
|
87 def __init__(self, host, target, type_=C.CHAT_ONE2ONE, occupants=None, subject=None, profiles=None): |
|
88 quick_chat.QuickChat.__init__(self, host, target, type_, occupants, subject, profiles=profiles) |
|
89 cagou_widget.CagouWidget.__init__(self) |
|
90 self.header_input.hint_text = u"You are talking with {}".format(target) |
|
91 scroll_view = ScrollView(size_hint=(1,0.8), scroll_y=0) |
|
92 self.messages_widget = MessagesWidget() |
|
93 scroll_view.add_widget(self.messages_widget) |
|
94 self.add_widget(scroll_view) |
|
95 message_input = MessageInputWidget() |
|
96 message_input.bind(on_text_validate=self.onSend) |
|
97 self.add_widget(message_input) |
|
98 self.postInit() |
|
99 |
|
100 @classmethod |
|
101 def factory(cls, plugin_info, target, profiles): |
|
102 profiles = list(profiles) |
|
103 if len(profiles) > 1: |
|
104 raise NotImplementedError(u"Multi-profiles is not available yet for chat") |
|
105 if target is None: |
|
106 target = G.host.profiles[profiles[0]].whoami |
|
107 return G.host.widgets.getOrCreateWidget(cls, target, on_new_widget=None, on_existing_widget=C.WIDGET_RECREATE, profiles=profiles) |
|
108 |
|
109 def messageDataConverter(self, idx, mess_id): |
|
110 return {"mess_data": self.messages[mess_id]} |
|
111 |
|
112 def _onHistoryPrinted(self): |
|
113 """Refresh or scroll down the focus after the history is printed""" |
|
114 # self.adapter.data = self.messages |
|
115 for mess_data in self.messages.itervalues(): |
|
116 self.appendMessage(mess_data) |
|
117 super(Chat, self)._onHistoryPrinted() |
|
118 |
|
119 def createMessage(self, message): |
|
120 self.appendMessage(message) |
|
121 |
|
122 def appendMessage(self, mess_data): |
|
123 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data)) |
|
124 |
|
125 def onSend(self, input_widget): |
|
126 G.host.messageSend( |
|
127 self.target, |
|
128 {'': input_widget.text}, # TODO: handle language |
|
129 mess_type = C.MESS_TYPE_GROUPCHAT if self.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT, # TODO: put this in QuickChat |
|
130 profile_key=self.profile |
|
131 ) |
|
132 input_widget.text = '' |
|
133 |
|
134 def onHeaderInput(self): |
|
135 text = self.header_input.text.strip() |
|
136 try: |
|
137 if text.count(u'@') != 1 or text.count(u' '): |
|
138 raise ValueError |
|
139 jid_ = jid.JID(text) |
|
140 except ValueError: |
|
141 log.info(u"entered text is not a jid") |
|
142 return |
|
143 |
|
144 def discoCb(disco): |
|
145 # TODO: check if plugin XEP-0045 is activated |
|
146 if "conference" in [i[0] for i in disco[1]]: |
|
147 raise NotImplementedError(u"MUC not implemented yet") |
|
148 # G.host.bridge.MUCJoin(unicode(jid_), "", "", self.profile) |
|
149 else: |
|
150 plugin_info = [p for p in G.host.getPluggedWidgets() if p["factory"] == self.factory][0] # FIXME: Q&D way, need a proper method in host |
|
151 factory = plugin_info['factory'] |
|
152 G.host.switchWidget(self, factory(plugin_info, jid_, profiles=[self.profile])) |
|
153 |
|
154 def discoEb(failure): |
|
155 log.warning(u"Disco failure, ignore this text: {}".format(failure)) |
|
156 |
|
157 G.host.bridge.discoInfos(jid_.domain, self.profile, callback=discoCb, errback=discoEb) |
|
158 |
|
159 |
|
160 |
|
161 PLUGIN_INFO["factory"] = Chat.factory |
|
162 quick_widgets.register(quick_chat.QuickChat, Chat) |