Mercurial > libervia-backend
comparison frontends/src/primitivus/chat.py @ 1265:e3a9ea76de35 frontends_multi_profiles
quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p):
This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments:
- profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions
- Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far
- all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour
- widgets are created in a dedicated manager, with facilities to react on new widget creation or other events
- quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles
- each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid
- better management of CHAT_GROUP mode for Chat widgets
- some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses
- no more (un)escapePrivate/PRIVATE_PREFIX
- contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed
- resources can be displayed in Primitivus, and their status messages
- profiles are managed in QuickFrontend with dedicated managers
This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 10 Dec 2014 19:00:09 +0100 |
parents | 802b7e6bf098 |
children | faa1129559b8 |
comparison
equal
deleted
inserted
replaced
1264:60dfa2f5d61f | 1265:e3a9ea76de35 |
---|---|
21 from sat.core import log as logging | 21 from sat.core import log as logging |
22 log = logging.getLogger(__name__) | 22 log = logging.getLogger(__name__) |
23 import urwid | 23 import urwid |
24 from urwid_satext import sat_widgets | 24 from urwid_satext import sat_widgets |
25 from urwid_satext.files_management import FileDialog | 25 from urwid_satext.files_management import FileDialog |
26 from sat_frontends.quick_frontend import quick_widgets | |
26 from sat_frontends.quick_frontend.quick_chat import QuickChat | 27 from sat_frontends.quick_frontend.quick_chat import QuickChat |
27 from sat_frontends.primitivus.card_game import CardGame | 28 from sat_frontends.primitivus.card_game import CardGame |
28 from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate | |
29 from sat_frontends.primitivus.constants import Const as C | 29 from sat_frontends.primitivus.constants import Const as C |
30 from sat_frontends.primitivus.keys import action_key_map as a_key | 30 from sat_frontends.primitivus.keys import action_key_map as a_key |
31 from sat_frontends.primitivus.widget import PrimitivusWidget | |
31 import time | 32 import time |
32 from sat_frontends.tools.jid import JID | 33 from sat_frontends.tools.jid import JID |
33 | 34 |
34 | 35 |
35 class ChatText(urwid.FlowWidget): | 36 class ChatText(urwid.FlowWidget): |
78 if self.is_info: | 79 if self.is_info: |
79 return urwid.AttrMap(txt_widget, 'info_msg') | 80 return urwid.AttrMap(txt_widget, 'info_msg') |
80 return txt_widget | 81 return txt_widget |
81 | 82 |
82 | 83 |
83 class Chat(urwid.WidgetWrap, QuickChat): | 84 class Chat(PrimitivusWidget, QuickChat): |
84 | 85 |
85 def __init__(self, target, host, type_='one2one'): | 86 def __init__(self, host, target, type_='one2one', profiles=None): |
86 self.target = target | 87 QuickChat.__init__(self, host, target, type_, profiles=profiles) |
87 QuickChat.__init__(self, target, host, type_) | |
88 self.content = urwid.SimpleListWalker([]) | 88 self.content = urwid.SimpleListWalker([]) |
89 self.text_list = urwid.ListBox(self.content) | 89 self.text_list = urwid.ListBox(self.content) |
90 self.chat_widget = urwid.Frame(self.text_list) | 90 self.chat_widget = urwid.Frame(self.text_list) |
91 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) | 91 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) |
92 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) | 92 self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)]) |
93 self.pile = urwid.Pile([self.chat_colums]) | 93 self.pile = urwid.Pile([self.chat_colums]) |
94 urwid.WidgetWrap.__init__(self, self.__getDecoration(self.pile)) | 94 PrimitivusWidget.__init__(self, self.pile, self.target) |
95 self.setType(type_) | 95 |
96 # we must adapt the behavious with the type | |
97 if type_ == 'one2one': | |
98 self.historyPrint(profile=self.profile) | |
99 elif type_ == 'group': | |
100 if len(self.chat_colums.contents) == 1: | |
101 present_widget = self._buildPresentList() | |
102 self.present_panel = sat_widgets.VerticalSeparator(present_widget) | |
103 self._appendPresentPanel() | |
104 | |
96 self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) #struct_time of day changing time | 105 self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00 %Y")) #struct_time of day changing time |
97 self.show_timestamp = True | 106 self.show_timestamp = True |
98 self.show_short_nick = False | 107 self.show_short_nick = False |
99 self.show_title = 1 #0: clip title; 1: full title; 2: no title | 108 self.show_title = 1 #0: clip title; 1: full title; 2: no title |
100 self.subject = None | 109 self.subject = None |
102 def keypress(self, size, key): | 111 def keypress(self, size, key): |
103 if key == a_key['OCCUPANTS_HIDE']: #user wants to (un)hide the presents panel | 112 if key == a_key['OCCUPANTS_HIDE']: #user wants to (un)hide the presents panel |
104 if self.type == 'group': | 113 if self.type == 'group': |
105 widgets = [widget for (widget, options) in self.chat_colums.contents] | 114 widgets = [widget for (widget, options) in self.chat_colums.contents] |
106 if self.present_panel in widgets: | 115 if self.present_panel in widgets: |
107 self.__removePresentPanel() | 116 self._removePresentPanel() |
108 else: | 117 else: |
109 self.__appendPresentPanel() | 118 self._appendPresentPanel() |
110 elif key == a_key['TIMESTAMP_HIDE']: #user wants to (un)hide timestamp | 119 elif key == a_key['TIMESTAMP_HIDE']: #user wants to (un)hide timestamp |
111 self.show_timestamp = not self.show_timestamp | 120 self.show_timestamp = not self.show_timestamp |
112 for wid in self.content: | 121 for wid in self.content: |
113 wid._invalidate() | 122 wid._invalidate() |
114 elif key == a_key['SHORT_NICKNAME']: #user wants to (not) use short nick | 123 elif key == a_key['SHORT_NICKNAME']: #user wants to (not) use short nick |
115 self.show_short_nick = not self.show_short_nick | 124 self.show_short_nick = not self.show_short_nick |
116 for wid in self.content: | 125 for wid in self.content: |
117 wid._invalidate() | 126 wid._invalidate() |
118 elif key == a_key['DECORATION_HIDE']: #user wants to (un)hide widget decoration | |
119 show = not isinstance(self._w, sat_widgets.LabelLine) | |
120 self.showDecoration(show) | |
121 self._invalidate() | |
122 elif key == a_key['SUBJECT_SWITCH']: #user wants to (un)hide group's subject or change its apperance | 127 elif key == a_key['SUBJECT_SWITCH']: #user wants to (un)hide group's subject or change its apperance |
123 if self.subject: | 128 if self.subject: |
124 self.show_title = (self.show_title + 1) % 3 | 129 self.show_title = (self.show_title + 1) % 3 |
125 if self.show_title == 0: | 130 if self.show_title == 0: |
126 self.setSubject(self.subject,'clip') | 131 self.setSubject(self.subject,'clip') |
128 self.setSubject(self.subject,'space') | 133 self.setSubject(self.subject,'space') |
129 elif self.show_title == 2: | 134 elif self.show_title == 2: |
130 self.chat_widget.header = None | 135 self.chat_widget.header = None |
131 self._invalidate() | 136 self._invalidate() |
132 | 137 |
133 | |
134 return super(Chat, self).keypress(size, key) | 138 return super(Chat, self).keypress(size, key) |
135 | 139 |
136 def getMenu(self): | 140 def getMenu(self): |
137 """Return Menu bar""" | 141 """Return Menu bar""" |
138 menu = sat_widgets.Menu(self.host.loop) | 142 menu = sat_widgets.Menu(self.host.loop) |
139 if self.type == 'group': | 143 if self.type == 'group': |
140 self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare}) | 144 self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare}) |
141 game = _("Game") | 145 game = _("Game") |
142 menu.addMenu(game, "Tarot", self.onTarotRequest) | 146 menu.addMenu(game, "Tarot", self.onTarotRequest) |
143 elif self.type == 'one2one': | 147 elif self.type == 'one2one': |
144 self.host.addMenus(menu, C.MENU_SINGLE, {'jid': unescapePrivate(self.target)}) | 148 self.host.addMenus(menu, C.MENU_SINGLE, {'jid': self.target}) |
145 menu.addMenu(_("Action"), _("Send file"), self.onSendFileRequest) | 149 menu.addMenu(_("Action"), _("Send file"), self.onSendFileRequest) |
146 return menu | 150 return menu |
147 | 151 |
148 def setType(self, type_): | 152 def updateChatState(self, from_jid, state): |
149 QuickChat.setType(self, type_) | 153 if self.type == C.CHAT_GROUP: |
150 if type_ == 'one2one': | 154 if from_jid == C.ENTITY_ALL: |
151 self.historyPrint(profile=self.host.profile) | 155 occupants = self.occupants |
152 elif type_ == 'group': | 156 else: |
153 if len(self.chat_colums.contents) == 1: | 157 nick = from_jid.resource |
154 present_widget = self.__buildPresentList() | 158 if not nick: |
155 self.present_panel = sat_widgets.VerticalSeparator(present_widget) | 159 log.debug("no nick found for chatstate") |
156 self.__appendPresentPanel() | 160 return |
157 | 161 occupants = [nick] |
158 def __getDecoration(self, widget): | |
159 return sat_widgets.LabelLine(widget, self.__getSurrendedText()) | |
160 | |
161 def __getSurrendedText(self): | |
162 """Get the text to be displayed as the dialog title.""" | |
163 if not hasattr(self, "surrended_text"): | |
164 self.__setSurrendedText() | |
165 return self.surrended_text | |
166 | |
167 def __setSurrendedText(self, state=None): | |
168 """Set the text to be displayed as the dialog title | |
169 @param stat: chat state of the contact | |
170 """ | |
171 text = unicode(unescapePrivate(self.target)) | |
172 if state: | |
173 text += " (" + state + ")" | |
174 self.surrended_text = sat_widgets.SurroundedText(text) | |
175 | |
176 def showDecoration(self, show=True): | |
177 """Show/Hide the decoration around the chat window""" | |
178 if show: | |
179 main_widget = self.__getDecoration(self.pile) | |
180 else: | |
181 main_widget = self.pile | |
182 self._w = main_widget | |
183 | |
184 def updateChatState(self, state, nick=None): | |
185 """Set the chat state (XEP-0085) of the contact. Leave nick to None | |
186 to set the state for a one2one conversation, or give a nickname or | |
187 C.ALL_OCCUPANTS to set the state of a participant within a MUC. | |
188 @param state: the new chat state | |
189 @param nick: None for one2one, the MUC user nick or C.ALL_OCCUPANTS | |
190 """ | |
191 if nick: | |
192 assert(self.type == 'group') | |
193 occupants = self.occupants if nick == C.ALL_OCCUPANTS else [nick] | |
194 options = self.present_wid.getAllValues() | 162 options = self.present_wid.getAllValues() |
195 for index in xrange(0, len(options)): | 163 for index in xrange(0, len(options)): |
196 nick = options[index].value | 164 nick = options[index].value |
197 if nick in occupants: | 165 if nick in occupants: |
198 options[index] = (nick, '%s %s' % (C.MUC_USER_STATES[state], nick)) | 166 options[index] = (nick, '%s %s' % (C.MUC_USER_STATES[state], nick)) |
199 self.present_wid.changeValues(options) | 167 self.present_wid.changeValues(options) |
200 self.host.redraw() | 168 self.host.redraw() |
201 else: | 169 else: |
202 assert(self.type == 'one2one') | 170 self.title_dynamic = '({})'.format(state) |
203 self.__setSurrendedText(state) | |
204 self.showDecoration() | |
205 self.host.redraw() | |
206 | 171 |
207 def _presentClicked(self, list_wid, clicked_wid): | 172 def _presentClicked(self, list_wid, clicked_wid): |
208 assert(self.type == 'group') | 173 assert self.type == 'group' |
209 nick = clicked_wid.getValue().value | 174 nick = clicked_wid.getValue().value |
210 if nick == self.getUserNick(): | 175 if nick == self.getUserNick(): |
211 #We ignore click on our own nick | 176 #We ignore clicks on our own nick |
212 return | 177 return |
213 #we have a click on a nick, we add the private conversation to the contact_list | 178 contact_list = self.host.contact_lists[self.profile] |
214 full_jid = JID("%s/%s" % (self.target.bare, nick)) | 179 full_jid = JID("%s/%s" % (self.target.bare, nick)) |
215 new_jid = escapePrivate(full_jid) | 180 |
216 if new_jid not in self.host.contact_list: | 181 #we have a click on a nick, we need to create the widget if it doesn't exists |
217 self.host.contact_list.add(new_jid, [C.GROUP_NOT_IN_ROSTER]) | 182 self.getOrCreatePrivateWidget(full_jid) |
218 | 183 |
219 #now we select the new window | 184 #now we select the new window |
220 self.host.contact_list.setFocus(full_jid, True) | 185 contact_list.setFocus(full_jid, True) |
221 | 186 |
222 def __buildPresentList(self): | 187 def _buildPresentList(self): |
223 self.present_wid = sat_widgets.GenericList([],option_type = sat_widgets.ClickableText, on_click=self._presentClicked) | 188 self.present_wid = sat_widgets.GenericList([],option_type = sat_widgets.ClickableText, on_click=self._presentClicked) |
224 return self.present_wid | 189 return self.present_wid |
225 | 190 |
226 def __appendPresentPanel(self): | 191 def _appendPresentPanel(self): |
227 self.chat_colums.contents.append((self.present_panel,('weight', 2, False))) | 192 self.chat_colums.contents.append((self.present_panel,('weight', 2, False))) |
228 | 193 |
229 def __removePresentPanel(self): | 194 def _removePresentPanel(self): |
230 for widget, options in self.chat_colums.contents: | 195 for widget, options in self.chat_colums.contents: |
231 if widget is self.present_panel: | 196 if widget is self.present_panel: |
232 self.chat_colums.contents.remove((widget, options)) | 197 self.chat_colums.contents.remove((widget, options)) |
233 break | 198 break |
234 | 199 |
235 def __appendGamePanel(self, widget): | 200 def _appendGamePanel(self, widget): |
236 assert (len(self.pile.contents) == 1) | 201 assert (len(self.pile.contents) == 1) |
237 self.pile.contents.insert(0,(widget,('weight', 1))) | 202 self.pile.contents.insert(0,(widget,('weight', 1))) |
238 self.pile.contents.insert(1,(urwid.Filler(urwid.Divider('-'),('fixed', 1)))) | 203 self.pile.contents.insert(1,(urwid.Filler(urwid.Divider('-'),('fixed', 1)))) |
239 self.host.redraw() | 204 self.host.redraw() |
240 | 205 |
241 def __removeGamePanel(self): | 206 def _removeGamePanel(self): |
242 assert (len(self.pile.contents) == 3) | 207 assert (len(self.pile.contents) == 3) |
243 del self.pile.contents[0] | 208 del self.pile.contents[0] |
244 self.host.redraw() | 209 self.host.redraw() |
245 | 210 |
246 def setSubject(self, subject, wrap='space'): | 211 def setSubject(self, subject, wrap='space'): |
288 """Refresh or scroll down the focus after the history is printed""" | 253 """Refresh or scroll down the focus after the history is printed""" |
289 if len(self.content): | 254 if len(self.content): |
290 self.text_list.focus_position = len(self.content) - 1 # scroll down | 255 self.text_list.focus_position = len(self.content) - 1 # scroll down |
291 self.host.redraw() | 256 self.host.redraw() |
292 | 257 |
258 def onPrivateCreated(self, widget): | |
259 self.host.contact_lists[widget.profile].specialResourceVisible(widget.target) | |
260 | |
293 def printMessage(self, from_jid, msg, profile, timestamp=None): | 261 def printMessage(self, from_jid, msg, profile, timestamp=None): |
294 assert isinstance(from_jid, JID) | 262 assert isinstance(from_jid, JID) |
295 try: | 263 try: |
296 jid, nick, mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp) | 264 jid, nick, mymess = QuickChat.printMessage(self, from_jid, msg, profile, timestamp) |
297 except TypeError: | 265 except TypeError: |
266 # None is returned, the message is managed | |
298 return | 267 return |
299 | |
300 new_text = ChatText(self, timestamp, nick, mymess, msg) | 268 new_text = ChatText(self, timestamp, nick, mymess, msg) |
301 | 269 |
302 if timestamp and self.content: | 270 if timestamp and self.content: |
303 for idx in range(len(self.content) - 1, -1, -1): | 271 for idx in range(len(self.content) - 1, -1, -1): |
304 current_text = self.content[idx] | 272 current_text = self.content[idx] |
355 | 323 |
356 def startGame(self, game_type, referee, players): | 324 def startGame(self, game_type, referee, players): |
357 """Configure the chat window to start a game""" | 325 """Configure the chat window to start a game""" |
358 if game_type=="Tarot": | 326 if game_type=="Tarot": |
359 self.tarot_wid = CardGame(self, referee, players, self.nick) | 327 self.tarot_wid = CardGame(self, referee, players, self.nick) |
360 self.__appendGamePanel(self.tarot_wid) | 328 self._appendGamePanel(self.tarot_wid) |
361 | 329 |
362 def getGame(self, game_type): | 330 def getGame(self, game_type): |
363 """Return class managing the game type""" | 331 """Return class managing the game type""" |
364 #TODO: check that the game is launched, and manage errors | 332 #TODO: check that the game is launched, and manage errors |
365 if game_type=="Tarot": | 333 if game_type=="Tarot": |
369 def onTarotRequest(self, menu): | 337 def onTarotRequest(self, menu): |
370 # TODO: move this to plugin_misc_tarot with dynamic menu | 338 # TODO: move this to plugin_misc_tarot with dynamic menu |
371 if len(self.occupants) != 4: | 339 if len(self.occupants) != 4: |
372 self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp)) | 340 self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp)) |
373 else: | 341 else: |
374 self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile) | 342 self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.profile) |
375 | 343 |
376 def onSendFileRequest(self, menu): | 344 def onSendFileRequest(self, menu): |
377 # TODO: move this to core with dynamic menus | 345 # TODO: move this to core with dynamic menus |
378 dialog = FileDialog(ok_cb=self.onFileSelected, cancel_cb=self.host.removePopUp) | 346 dialog = FileDialog(ok_cb=self.onFileSelected, cancel_cb=self.host.removePopUp) |
379 self.host.showPopUp(dialog, 80, 80) | 347 self.host.showPopUp(dialog, 80, 80) |
386 except UnicodeError: | 354 except UnicodeError: |
387 log.error("FIXME: filepath with unicode error are not managed yet") | 355 log.error("FIXME: filepath with unicode error are not managed yet") |
388 self.host.showDialog(_(u"File has a unicode error in its name, it's not yet managed by SàT"), title=_("Can't send file"), type_="error") | 356 self.host.showDialog(_(u"File has a unicode error in its name, it's not yet managed by SàT"), title=_("Can't send file"), type_="error") |
389 return | 357 return |
390 #FIXME: check last_resource: what if self.target.resource exists ? | 358 #FIXME: check last_resource: what if self.target.resource exists ? |
391 last_resource = self.host.bridge.getLastResource(unicode(self.target.bare), self.host.profile) | 359 last_resource = self.host.bridge.getLastResource(unicode(self.target.bare), self.profile) |
392 if last_resource: | 360 if last_resource: |
393 full_jid = JID("%s/%s" % (self.target.bare, last_resource)) | 361 full_jid = JID("%s/%s" % (self.target.bare, last_resource)) |
394 else: | 362 else: |
395 full_jid = self.target | 363 full_jid = self.target |
396 progress_id = self.host.bridge.sendFile(full_jid, filepath, {}, self.host.profile) | 364 progress_id = self.host.bridge.sendFile(full_jid, filepath, {}, self.profile) |
397 self.host.addProgress(progress_id,filepath) | 365 self.host.addProgress(progress_id,filepath) |
398 self.host.showDialog(_(u"You file request has been sent, we are waiting for your contact answer"), title=_("File request sent")) | 366 self.host.showDialog(_(u"You file request has been sent, we are waiting for your contact answer"), title=_("File request sent")) |
367 | |
368 | |
369 quick_widgets.register(QuickChat, Chat) |