comparison frontends/src/primitivus/primitivus @ 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 82dabb442e2e
children faa1129559b8
comparison
equal deleted inserted replaced
1264:60dfa2f5d61f 1265:e3a9ea76de35
26 log = logging.getLogger(__name__) 26 log = logging.getLogger(__name__)
27 import urwid 27 import urwid
28 from urwid_satext import sat_widgets 28 from urwid_satext import sat_widgets
29 from urwid_satext.files_management import FileDialog 29 from urwid_satext.files_management import FileDialog
30 from sat_frontends.quick_frontend.quick_app import QuickApp 30 from sat_frontends.quick_frontend.quick_app import QuickApp
31 from sat_frontends.quick_frontend.quick_chat_list import QuickChatList 31 from sat_frontends.quick_frontend.quick_utils import getNewPath
32 from sat_frontends.quick_frontend.quick_utils import getNewPath, unescapePrivate 32 from sat_frontends.quick_frontend import quick_chat
33 from sat_frontends.primitivus.profile_manager import ProfileManager 33 from sat_frontends.primitivus.profile_manager import ProfileManager
34 from sat_frontends.primitivus.contact_list import ContactList 34 from sat_frontends.primitivus.contact_list import ContactList
35 from sat_frontends.primitivus.chat import Chat 35 from sat_frontends.primitivus.chat import Chat
36 from sat_frontends.primitivus import xmlui 36 from sat_frontends.primitivus import xmlui
37 from sat_frontends.primitivus.progress import Progress 37 from sat_frontends.primitivus.progress import Progress
38 from sat_frontends.primitivus.notify import Notify 38 from sat_frontends.primitivus.notify import Notify
39 from sat_frontends.primitivus.keys import action_key_map as a_key 39 from sat_frontends.primitivus.keys import action_key_map as a_key
40 from sat_frontends.primitivus import config 40 from sat_frontends.primitivus import config
41 from sat_frontends.tools.misc import InputHistory 41 from sat_frontends.tools.misc import InputHistory
42 from sat_frontends.constants import Const as commonConst # FIXME 42 from sat_frontends.tools import jid
43 from sat_frontends.tools.jid import JID
44 from os.path import join 43 from os.path import join
45 import signal 44 import signal
46 45
47
48 class ChatList(QuickChatList):
49 """This class manage the list of chat windows"""
50
51 def createChat(self, target):
52 return Chat(target, self.host)
53 46
54 47
55 class EditBar(sat_widgets.ModalEdit): 48 class EditBar(sat_widgets.ModalEdit):
56 """ 49 """
57 The modal edit bar where you would enter messages and commands. 50 The modal edit bar where you would enter messages and commands.
58 """ 51 """
59 52
60 def __init__(self, app): 53 def __init__(self, host):
61 modes = {None: ('NORMAL', u''), 54 modes = {None: (C.MODE_NORMAL, u''),
62 a_key['MODE_INSERTION']: ('INSERTION', u'> '), 55 a_key['MODE_INSERTION']: (C.MODE_INSERTION, u'> '),
63 a_key['MODE_COMMAND']: ('COMMAND', u':')} #XXX: captions *MUST* be unicode 56 a_key['MODE_COMMAND']: (C.MODE_COMMAND, u':')} #XXX: captions *MUST* be unicode
64 super(EditBar, self).__init__(modes) 57 super(EditBar, self).__init__(modes)
65 self.app = app 58 self.host = host
66 self.setCompletionMethod(self._text_completion) 59 self.setCompletionMethod(self._text_completion)
67 urwid.connect_signal(self, 'click', self.onTextEntered) 60 urwid.connect_signal(self, 'click', self.onTextEntered)
68 61
69 def _text_completion(self, text, completion_data, mode): 62 def _text_completion(self, text, completion_data, mode):
70 if mode == 'INSERTION': 63 if mode == C.MODE_INSERTION:
71 return self._nick_completion(text, completion_data) 64 return self._nick_completion(text, completion_data)
72 else: 65 else:
73 return text 66 return text
74 67
75 def _nick_completion(self, text, completion_data): 68 def _nick_completion(self, text, completion_data):
76 """Completion method which complete pseudo in group chat 69 """Completion method which complete pseudo in group chat
77 for params, see AdvancedEdit""" 70 for params, see AdvancedEdit"""
78 contact = self.app.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once 71 contact = self.host.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once
79 if contact: 72 if contact:
80 chat = self.app.chat_wins[contact] 73 chat = self.host.chat_wins[contact]
81 if chat.type != "group": 74 if chat.type != "group":
82 return text 75 return text
83 space = text.rfind(" ") 76 space = text.rfind(" ")
84 start = text[space+1:] 77 start = text[space+1:]
85 nicks = list(chat.occupants) 78 nicks = list(chat.occupants)
96 return text[:space+1] + nicks[idx] + (': ' if space < 0 else '') 89 return text[:space+1] + nicks[idx] + (': ' if space < 0 else '')
97 return text 90 return text
98 91
99 def onTextEntered(self, editBar): 92 def onTextEntered(self, editBar):
100 """Called when text is entered in the main edit bar""" 93 """Called when text is entered in the main edit bar"""
101 if self.mode == 'INSERTION': 94 if self.mode == C.MODE_INSERTION:
102 contact = self.app.contact_list.getContact() ###Based on the fact that there is currently only one contact selectableat once 95 if isinstance(self.host.selected_widget, quick_chat.QuickChat):
103 if contact: 96 chat_widget = self.host.selected_widget
104 chat = self.app.chat_wins[contact] 97 self.host.sendMessage(chat_widget.target,
105 self.app.sendMessage(contact,
106 editBar.get_edit_text(), 98 editBar.get_edit_text(),
107 mess_type = "groupchat" if chat.type == 'group' else "chat", 99 mess_type = "groupchat" if chat_widget.type == 'group' else "chat", # TODO: put this in QuickChat
108 errback=lambda failure: self.app.notify(_("Error while sending message (%s)") % failure), 100 errback=lambda failure: self.host.notify(_("Error while sending message ({})").format(failure)),
109 profile_key=self.app.profile 101 profile_key=chat_widget.profile
110 ) 102 )
111 editBar.set_edit_text('') 103 editBar.set_edit_text('')
112 elif self.mode == 'COMMAND': 104 elif self.mode == C.MODE_COMMAND:
113 self.commandHandler() 105 self.commandHandler()
114 106
115 def commandHandler(self): 107 def commandHandler(self):
116 #TODO: separate class with auto documentation (with introspection) 108 #TODO: separate class with auto documentation (with introspection)
117 # and completion method 109 # and completion method
118 tokens = self.get_edit_text().split(' ') 110 tokens = self.get_edit_text().split(' ')
119 command, args = tokens[0], tokens[1:] 111 command, args = tokens[0], tokens[1:]
120 if command == 'quit': 112 if command == 'quit':
121 self.app.onExit() 113 self.host.onExit()
122 raise urwid.ExitMainLoop() 114 raise urwid.ExitMainLoop()
123 elif command == 'messages': 115 elif command == 'messages':
124 wid = sat_widgets.GenericList(logging.memoryGet()) 116 wid = sat_widgets.GenericList(logging.memoryGet())
125 self.app.addWindow(wid) 117 self.host.selectWidget(wid)
126 elif command == 'presence': 118 # elif command == 'presence':
127 values = [value for value in commonConst.PRESENCE.keys()] 119 # values = [value for value in commonConst.PRESENCE.keys()]
128 values = [value if value else 'online' for value in values] # the empty value actually means 'online' 120 # values = [value if value else 'online' for value in values] # the empty value actually means 'online'
129 if args and args[0] in values: 121 # if args and args[0] in values:
130 presence = '' if args[0] == 'online' else args[0] 122 # presence = '' if args[0] == 'online' else args[0]
131 self.app.status_bar.onChange(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence])) 123 # self.host.status_bar.onChange(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence]))
132 else: 124 # else:
133 self.app.status_bar.onPresenceClick() 125 # self.host.status_bar.onPresenceClick()
134 elif command == 'status': 126 # elif command == 'status':
135 if args: 127 # if args:
136 self.app.status_bar.onChange(user_data=sat_widgets.AdvancedEdit(args[0])) 128 # self.host.status_bar.onChange(user_data=sat_widgets.AdvancedEdit(args[0]))
137 else: 129 # else:
138 self.app.status_bar.onStatusClick() 130 # self.host.status_bar.onStatusClick()
139 elif command == 'history': 131 elif command == 'history':
140 try: 132 widget = self.host.selected_widget
141 limit = int(args[0]) 133 if isinstance(widget, quick_chat.QuickChat):
142 except (IndexError, ValueError): 134 try:
143 limit = 50 135 limit = int(args[0])
144 win = self.app.chat_wins[JID(self.app.contact_list.selected).bare] 136 except (IndexError, ValueError):
145 win.clearHistory() 137 limit = 50
146 if limit > 0: 138 widget.clearHistory()
147 win.historyPrint(size=limit, profile=self.app.profile) 139 if limit > 0:
140 widget.historyPrint(size=limit, profile=widget.profile)
148 elif command == 'search': 141 elif command == 'search':
149 pattern = " ".join(args) 142 widget = self.host.selected_widget
150 if not pattern: 143 if isinstance(widget, quick_chat.QuickChat):
151 self.app.notif_bar.addMessage(D_("Please specify the globbing pattern to search for")) 144 pattern = " ".join(args)
152 win = self.app.chat_wins[JID(self.app.contact_list.selected).bare] 145 if not pattern:
153 win.clearHistory() 146 self.host.notif_bar.addMessage(D_("Please specify the globbing pattern to search for"))
154 win.printInfo(D_("Results for searching the globbing pattern: %s") % pattern, timestamp=0) 147 widget.clearHistory()
155 win.historyPrint(size=C.HISTORY_LIMIT_NONE, search=pattern, profile=self.app.profile) 148 widget.printInfo(D_("Results for searching the globbing pattern: %s") % pattern, timestamp=0)
156 win.printInfo(D_("Type ':history <lines>' to reset the chat history")) 149 widget.historyPrint(size=C.HISTORY_LIMIT_NONE, search=pattern, profile=widget.profile)
150 widget.printInfo(D_("Type ':history <lines>' to reset the chat history"))
157 else: 151 else:
158 return 152 return
159 self.set_edit_text('') 153 self.set_edit_text('')
160 154
161 def _historyCb(self, text): 155 def _historyCb(self, text):
165 def keypress(self, size, key): 159 def keypress(self, size, key):
166 """Callback when a key is pressed. Send "composing" states 160 """Callback when a key is pressed. Send "composing" states
167 and move the index of the temporary history stack.""" 161 and move the index of the temporary history stack."""
168 if key == a_key['MODAL_ESCAPE']: 162 if key == a_key['MODAL_ESCAPE']:
169 # first save the text to the current mode, then change to NORMAL 163 # first save the text to the current mode, then change to NORMAL
170 self.app._updateInputHistory(self.get_edit_text(), mode=self.mode) 164 self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
171 self.app._updateInputHistory(mode='NORMAL') 165 self.host._updateInputHistory(mode=C.MODE_NORMAL)
172 if self._mode == 'NORMAL' and key in self._modes: 166 if self._mode == C.MODE_NORMAL and key in self._modes:
173 self.app._updateInputHistory(mode=self._modes[key][0]) 167 self.host._updateInputHistory(mode=self._modes[key][0])
174 if key == a_key['HISTORY_PREV']: 168 if key == a_key['HISTORY_PREV']:
175 self.app._updateInputHistory(self.get_edit_text(), -1, self._historyCb, self.mode) 169 self.host._updateInputHistory(self.get_edit_text(), -1, self._historyCb, self.mode)
176 return 170 return
177 elif key == a_key['HISTORY_NEXT']: 171 elif key == a_key['HISTORY_NEXT']:
178 self.app._updateInputHistory(self.get_edit_text(), +1, self._historyCb, self.mode) 172 self.host._updateInputHistory(self.get_edit_text(), +1, self._historyCb, self.mode)
179 return 173 return
180 elif key == a_key['EDIT_ENTER']: 174 elif key == a_key['EDIT_ENTER']:
181 self.app._updateInputHistory(self.get_edit_text(), mode=self.mode) 175 self.host._updateInputHistory(self.get_edit_text(), mode=self.mode)
182 else: 176 else:
183 contact = self.app.contact_list.getContact() 177 if (self._mode == C.MODE_INSERTION
184 if contact: 178 and isinstance(self.host.selected_widget, quick_chat.QuickChat)
185 self.app.bridge.chatStateComposing(unescapePrivate(contact), self.app.profile) 179 and key not in sat_widgets.FOCUS_KEYS):
180 self.host.bridge.chatStateComposing(self.host.selected_widget.target, self.host.selected_widget.profile)
181
186 return super(EditBar, self).keypress(size, key) 182 return super(EditBar, self).keypress(size, key)
187 183
188 184
189 class PrimitivusTopWidget(sat_widgets.FocusPile): 185 class PrimitivusTopWidget(sat_widgets.FocusPile):
190 """Top most widget used in Primitivus""" 186 """Top most widget used in Primitivus"""
285 281
286 ## main loop setup ## 282 ## main loop setup ##
287 self.main_widget = ProfileManager(self) 283 self.main_widget = ProfileManager(self)
288 self.loop = urwid.MainLoop(self.main_widget, C.PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler) 284 self.loop = urwid.MainLoop(self.main_widget, C.PALETTE, event_loop=urwid.GLibEventLoop(), input_filter=self.inputFilter, unhandled_input=self.keyHandler)
289 285
286
290 ##misc setup## 287 ##misc setup##
291 self.chat_wins = ChatList(self)
292 self.notif_bar = sat_widgets.NotificationBar() 288 self.notif_bar = sat_widgets.NotificationBar()
293 urwid.connect_signal(self.notif_bar, 'change', self.onNotification) 289 urwid.connect_signal(self.notif_bar, 'change', self.onNotification)
294 self.progress_wid = Progress(self) 290
295 urwid.connect_signal(self.notif_bar.progress, 'click', lambda x: self.addWindow(self.progress_wid)) 291 self.progress_wid = self.widgets.getOrCreateWidget(Progress, None, on_new_widget=None)
292 urwid.connect_signal(self.notif_bar.progress, 'click', lambda x: self.selectWidget(self.progress_wid))
296 self.__saved_overlay = None 293 self.__saved_overlay = None
297 294
298 self.x_notify = Notify() 295 self.x_notify = Notify()
299 296
300 # we already manage exit with a_key['APP_QUIT'], so we don't want C-c 297 # we already manage exit with a_key['APP_QUIT'], so we don't want C-c
349 popup = sat_widgets.Alert(_("Configuration Error"), _("Something went wrong while reading the configuration, please check :messages"), ok_cb=self.removePopUp) 346 popup = sat_widgets.Alert(_("Configuration Error"), _("Something went wrong while reading the configuration, please check :messages"), ok_cb=self.removePopUp)
350 if self.options.profile: 347 if self.options.profile:
351 self._early_popup = popup 348 self._early_popup = popup
352 else: 349 else:
353 self.showPopUp(popup) 350 self.showPopUp(popup)
354 super(PrimitivusApp, self).postInit() 351 super(PrimitivusApp, self).postInit(self.main_widget)
355 352
356 def inputFilter(self, input_, raw): 353 def inputFilter(self, input_, raw):
357 if self.__saved_overlay and input_ != a_key['OVERLAY_HIDE']: 354 if self.__saved_overlay and input_ != a_key['OVERLAY_HIDE']:
358 return 355 return
359 for i in input_: 356 for i in input_:
385 self.loop.widget = self.__saved_overlay 382 self.loop.widget = self.__saved_overlay
386 self.__saved_overlay = None 383 self.__saved_overlay = None
387 384
388 elif input_ == a_key['DEBUG'] and 'D' in self.bridge.getVersion(): #Debug only for dev versions 385 elif input_ == a_key['DEBUG'] and 'D' in self.bridge.getVersion(): #Debug only for dev versions
389 self.debug() 386 self.debug()
390 elif input_ == a_key['CONTACTS_HIDE']: #user wants to (un)hide the contact_list 387 elif input_ == a_key['CONTACTS_HIDE']: #user wants to (un)hide the contact lists
391 try: 388 try:
392 for wid, options in self.center_part.contents: 389 for wid, options in self.center_part.contents:
393 if self.contact_list is wid: 390 if self.contact_lists_pile is wid:
394 self.center_part.contents.remove((wid, options)) 391 self.center_part.contents.remove((wid, options))
395 break 392 break
396 else: 393 else:
397 self.center_part.contents.insert(0, (self.contact_list, ('weight', 2, False))) 394 self.center_part.contents.insert(0, (self.contact_lists_pile, ('weight', 2, False)))
398 except AttributeError: 395 except AttributeError:
399 #The main widget is not built (probably in Profile Manager) 396 #The main widget is not built (probably in Profile Manager)
400 pass 397 pass
401 elif input_ == 'window resize': 398 elif input_ == 'window resize':
402 width,height = self.loop.screen_size 399 width,height = self.loop.screen_size
411 try: 408 try:
412 return self.menu_roller.checkShortcuts(input_) 409 return self.menu_roller.checkShortcuts(input_)
413 except AttributeError: 410 except AttributeError:
414 return input_ 411 return input_
415 412
416 def addMenus(self, menu, type_, menu_data=None): 413 def addMenus(self, menu, type_filter, menu_data=None):
417 """Add cached menus to instance 414 """Add cached menus to instance
418 @param menu: sat_widgets.Menu instance 415 @param menu: sat_widgets.Menu instance
419 @param type_: menu type like is sat.core.sat_main.importMenu 416 @param type_filter: menu type like is sat.core.sat_main.importMenu
420 @param menu_data: data to send with these menus 417 @param menu_data: data to send with these menus
421 418
422 """ 419 """
423 menus = self.profiles[self.profile]['menus'].get(type_,[])
424 def add_menu_cb(callback_id): 420 def add_menu_cb(callback_id):
425 self.launchAction(callback_id, menu_data, profile_key = self.profile) 421 self.launchAction(callback_id, menu_data, profile=self.current_profile)
426 for id_, path, path_i18n in menus: 422 for id_, type_, path, path_i18n in self.bridge.getMenus("", C.NO_SECURITY_LIMIT ):
423 if type_ != type_filter:
424 continue
427 if len(path) != 2: 425 if len(path) != 2:
428 raise NotImplementedError("Menu with a path != 2 are not implemented yet") 426 raise NotImplementedError("Menu with a path != 2 are not implemented yet")
429 menu.addMenu(path_i18n[0], path_i18n[1], lambda dummy,id_=id_: add_menu_cb(id_)) 427 menu.addMenu(path_i18n[0], path_i18n[1], lambda dummy,id_=id_: add_menu_cb(id_))
430 428
431 429
443 menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, a_key['ROOM_JOIN']) 441 menu.addMenu(communication, _("Join room"), self.onJoinRoomRequest, a_key['ROOM_JOIN'])
444 #additionals menus 442 #additionals menus
445 #FIXME: do this in a more generic way (in quickapp) 443 #FIXME: do this in a more generic way (in quickapp)
446 self.addMenus(menu, C.MENU_GLOBAL) 444 self.addMenus(menu, C.MENU_GLOBAL)
447 445
448 menu_roller = sat_widgets.MenuRoller([(_('Main menu'),menu)]) 446 menu_roller = sat_widgets.MenuRoller([(_('Main menu'), menu, C.MENU_ID_MAIN)])
449 return menu_roller 447 return menu_roller
450 448
451 def _buildMainWidget(self): 449 def _buildMainWidget(self):
452 self.contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw()) 450 self.contact_lists_pile = urwid.Pile([])
453 #self.center_part = urwid.Columns([('weight',2,self.contact_list),('weight',8,Chat('',self))]) 451 #self.center_part = urwid.Columns([('weight',2,self.contact_lists[profile]),('weight',8,Chat('',self))])
454 self.center_part = urwid.Columns([('weight', 2, self.contact_list), ('weight', 8, urwid.Filler(urwid.Text('')))]) 452 self.center_part = urwid.Columns([('weight', 2, self.contact_lists_pile), ('weight', 8, urwid.Filler(urwid.Text('')))])
455 453
456 self.editBar = EditBar(self) 454 self.editBar = EditBar(self)
457 self.menu_roller = self._buildMenuRoller() 455 self.menu_roller = self._buildMenuRoller()
458 self.main_widget = PrimitivusTopWidget(self.center_part, self.menu_roller, self.notif_bar, self.editBar) 456 self.main_widget = PrimitivusTopWidget(self.center_part, self.menu_roller, self.notif_bar, self.editBar)
459 return self.main_widget 457 return self.main_widget
460 458
461 def plug_profile_1(self, profile_key='@DEFAULT@'): 459 def addContactList(self, profile):
460 contact_list = ContactList(self, on_click=self.contactSelected, on_change=lambda w: self.redraw(), profile=profile)
461 self.contact_lists_pile.contents.append((contact_list, ('weight', 1)))
462 self.contact_lists[profile] = contact_list
463 return contact_list
464
465 def plugging_profiles(self):
462 self.loop.widget = self._buildMainWidget() 466 self.loop.widget = self._buildMainWidget()
463 self.redraw() 467 self.redraw()
464 QuickApp.plug_profile_1(self, profile_key)
465 try: 468 try:
466 # if a popup arrived before main widget is build, we need to show it now 469 # if a popup arrived before main widget is build, we need to show it now
467 self.showPopUp(self._early_popup) 470 self.showPopUp(self._early_popup)
468 except AttributeError: 471 except AttributeError:
469 pass 472 pass
490 def notify(self, message): 493 def notify(self, message):
491 """"Notify message to user via notification bar""" 494 """"Notify message to user via notification bar"""
492 self.notif_bar.addMessage(message) 495 self.notif_bar.addMessage(message)
493 self.redraw() 496 self.redraw()
494 497
495 def addWindow(self, widget): 498 def newWidget(self, widget):
496 """Display a window if possible, 499 """Display a widget if possible,
500
497 else add it in the notification bar queue 501 else add it in the notification bar queue
498 @param widget: BoxWidget""" 502 @param widget: BoxWidget
499 assert(len(self.center_part.widget_list)<=2) 503 """
504 self.selectWidget(widget)
505
506 def selectWidget(self, widget):
507 assert len(self.center_part.widget_list)<=2
500 wid_idx = len(self.center_part.widget_list)-1 508 wid_idx = len(self.center_part.widget_list)-1
501 self.center_part.widget_list[wid_idx] = widget 509 self.center_part.widget_list[wid_idx] = widget
502 self.menu_roller.removeMenu(_('Chat menu')) 510 try:
503 self.contact_list.unselectAll() 511 self.menu_roller.removeMenu(C.MENU_ID_WIDGET)
512 except KeyError:
513 log.debug("No menu to delete")
514 self.selected_widget = widget
515 self.visible_widgets = set([widget]) # XXX: we can only have one widget visible at the time for now
516 for contact_list in self.contact_lists.itervalues():
517 contact_list.unselectAll()
518
519 for wid in self.visible_widgets:
520 if isinstance(wid, Chat):
521 contact_list = self.contact_lists[wid.profile]
522 contact_list.select(wid.target)
523
504 self.redraw() 524 self.redraw()
505 525
506 def removeWindow(self): 526 def removeWindow(self):
507 """Remove window showed on the right column""" 527 """Remove window showed on the right column"""
508 #TODO: to a better Window management than this crappy hack 528 #TODO: to a better Window management than this crappy hack
520 540
521 def setProgress(self, percentage): 541 def setProgress(self, percentage):
522 """Set the progression shown in notification bar""" 542 """Set the progression shown in notification bar"""
523 self.notif_bar.setProgress(percentage) 543 self.notif_bar.setProgress(percentage)
524 544
525 def contactSelected(self, contact_list): 545 def contactSelected(self, contact_list, entity):
526 contact = contact_list.getContact() 546 if entity.resource:
527 if contact: 547 # we have clicked on a private MUC conversation
528 assert(len(self.center_part.widget_list)==2) 548 chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, force_hash = Chat.getPrivateHash(contact_list.profile, entity), profile=contact_list.profile)
529 self.center_part.widget_list[1] = self.chat_wins[contact] 549 else:
530 self.menu_roller.addMenu(_('Chat menu'), self.chat_wins[contact].getMenu()) 550 chat_widget = self.widgets.getOrCreateWidget(Chat, entity, on_new_widget=None, profile=contact_list.profile)
531 551 self.selectWidget(chat_widget)
532 def newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile): 552 self.menu_roller.addMenu(_('Chat menu'), chat_widget.getMenu(), C.MENU_ID_WIDGET)
533 QuickApp.newMessageHandler(self, from_jid, to_jid, msg, _type, extra, profile) 553
534 554 def newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile):
535 if not from_jid in self.contact_list and from_jid.bare != self.profiles[profile]['whoami'].bare: 555 QuickApp.newMessageHandler(self, from_jid, to_jid, msg, type_, extra, profile)
556
557 if not from_jid in self.contact_lists[profile] and from_jid.bare != self.profiles[profile].whoami.bare:
536 #XXX: needed to show entities which haven't sent any 558 #XXX: needed to show entities which haven't sent any
537 # presence information and which are not in roster 559 # presence information and which are not in roster
538 self.contact_list.replace(from_jid, [C.GROUP_NOT_IN_ROSTER]) 560 self.contact_lists[profile].setContact(from_jid)
539 561 visible = False
540 if self.contact_list.selected is None or JID(self.contact_list.selected).bare != from_jid.bare: 562 for widget in self.visible_widgets:
541 self.contact_list.putAlert(from_jid) 563 if isinstance(widget, Chat) and widget.manageMessage(from_jid, type_):
564 visible = True
565 break
566 if not visible:
567 self.contact_lists[profile].setAlert(from_jid.bare if type_ == C.MESS_TYPE_GROUPCHAT else from_jid)
542 568
543 def _dialogOkCb(self, widget, data): 569 def _dialogOkCb(self, widget, data):
544 self.removePopUp() 570 self.removePopUp()
545 answer_cb = data[0] 571 answer_cb = data[0]
546 answer_data = [data[1]] if data[1] else [] 572 answer_data = [data[1]] if data[1] else []
549 def _dialogCancelCb(self, widget, data): 575 def _dialogCancelCb(self, widget, data):
550 self.removePopUp() 576 self.removePopUp()
551 answer_cb = data[0] 577 answer_cb = data[0]
552 answer_data = [data[1]] if data[1] else [] 578 answer_data = [data[1]] if data[1] else []
553 answer_cb(False, *answer_data) 579 answer_cb(False, *answer_data)
554
555 580
556 def showDialog(self, message, title="", type_="info", answer_cb = None, answer_data = None): 581 def showDialog(self, message, title="", type_="info", answer_cb = None, answer_data = None):
557 if type_ == 'info': 582 if type_ == 'info':
558 popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore 583 popup = sat_widgets.Alert(unicode(title), unicode(message), ok_cb=answer_cb or self.removePopUp) #FIXME: remove unicode here when DBus Bridge will no return dbus.String anymore
559 elif type_ == 'error': 584 elif type_ == 'error':
576 #No notification left, we can hide the bar 601 #No notification left, we can hide the bar
577 self.main_widget.hide('notif_bar') 602 self.main_widget.hide('notif_bar')
578 else: 603 else:
579 self.main_widget.show('notif_bar') 604 self.main_widget.show('notif_bar')
580 605
581 def launchAction(self, callback_id, data=None, profile_key="@NONE@"): 606 def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE):
582 """ Launch a dynamic action 607 """ Launch a dynamic action
583 @param callback_id: id of the action to launch 608 @param callback_id: id of the action to launch
584 @param data: data needed only for certain actions 609 @param data: data needed only for certain actions
585 @param profile_key: %(doc_profile_key)s 610 @param callback: if not None and 'validated' key is present, it will be called with the following parameters:
611 - callback_id
612 - data
613 - profile
614 @param profile: %(doc_profile)s
586 615
587 """ 616 """
588 if data is None: 617 if data is None:
589 data = dict() 618 data = dict()
590 619
591 def action_cb(data): 620 def action_cb(data):
592 if not data: 621 if not data:
593 # action was a one shot, nothing to do 622 # action was a one shot, nothing to do
594 pass 623 pass
595 elif "xmlui" in data: 624 elif "xmlui" in data:
596 ui = xmlui.create(self, xml_data=data['xmlui']) 625 ui = xmlui.create(self, xml_data=data['xmlui'], callback=callback, profile=profile)
597 ui.show() 626 ui.show()
598 elif "authenticated_profile" in data: 627 elif 'validated' in data:
599 assert("caller" in data) 628 pass # this key is managed below
600 if data["caller"] == "profile_manager":
601 assert(isinstance(self.main_widget, ProfileManager))
602 self.main_widget.getXMPPParams(data['authenticated_profile'])
603 elif data["caller"] == "plug_profile":
604 self.plug_profile_1(data['authenticated_profile'])
605 else:
606 raise NotImplementedError
607 else: 629 else:
608 self.showPopUp(sat_widgets.Alert(_("Error"), _(u"Unmanaged action result"), ok_cb=self.removePopUp)) 630 self.showPopUp(sat_widgets.Alert(_("Error"), _(u"Unmanaged action result"), ok_cb=self.removePopUp))
609 631
632 if callback and 'validated' in data:
633 callback(callback_id, data, profile)
634
610 def action_eb(failure): 635 def action_eb(failure):
611 self.showPopUp(sat_widgets.Alert(failure.fullname, failure.message, ok_cb=self.removePopUp)) 636 self.showPopUp(sat_widgets.Alert(failure.fullname, failure.message, ok_cb=self.removePopUp))
612 637
613 self.bridge.launchAction(callback_id, data, profile_key, callback=action_cb, errback=action_eb) 638 self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=action_eb)
614 639
615 def askConfirmationHandler(self, confirmation_id, confirmation_type, data, profile): 640 def askConfirmationHandler(self, confirmation_id, confirmation_type, data, profile):
616 answer_data={} 641 answer_data={}
617 642
618 def dir_selected_cb(path): 643 def dir_selected_cb(path):
682 callback(data) 707 callback(data)
683 else: 708 else:
684 log.error (_("FIXME FIXME FIXME: type [%s] not implemented") % type_) 709 log.error (_("FIXME FIXME FIXME: type [%s] not implemented") % type_)
685 raise NotImplementedError 710 raise NotImplementedError
686 711
712
713 def roomJoinedHandler(self, room_jid_s, room_nicks, user_nick, profile):
714 super(PrimitivusApp, self).roomJoinedHandler(room_jid_s, room_nicks, user_nick, profile)
715 self.contact_lists[profile].setFocus(jid.JID(room_jid_s), True)
716
717
718
687 ##DIALOGS CALLBACKS## 719 ##DIALOGS CALLBACKS##
688 def onJoinRoom(self, button, edit): 720 def onJoinRoom(self, button, edit):
689 self.removePopUp() 721 self.removePopUp()
690 room_jid = JID(edit.get_edit_text()) 722 room_jid = jid.JID(edit.get_edit_text())
691 if room_jid.is_valid(): 723 if room_jid.is_valid():
692 self.bridge.joinMUC(room_jid, self.profiles[self.profile]['whoami'].node, {}, self.profile) 724 self.bridge.joinMUC(room_jid, self.profiles[self.current_profile].whoami.node, {}, self.current_profile)
693 else: 725 else:
694 message = _("'%s' is an invalid JID !") % room_jid 726 message = _("'%s' is an invalid jid.JID !") % room_jid
695 log.error (message) 727 log.error (message)
696 self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp)) 728 self.showPopUp(sat_widgets.Alert(_("Error"), message, ok_cb=self.removePopUp))
697 729
698 #MENU EVENTS# 730 #MENU EVENTS#
699 def onConnectRequest(self, menu): 731 def onConnectRequest(self, menu):
700 QuickApp.asyncConnect(self, self.profile) 732 QuickApp.asyncConnect(self, self.current_profile)
701 733
702 def onDisconnectRequest(self, menu): 734 def onDisconnectRequest(self, menu):
703 self.bridge.disconnect(self.profile) 735 self.bridge.disconnect(self.current_profile)
704 736
705 def onParam(self, menu): 737 def onParam(self, menu):
706 def success(params): 738 def success(params):
707 ui = xmlui.create(self, xml_data=params) 739 ui = xmlui.create(self, xml_data=params)
708 ui.show() 740 ui.show()
709 741
710 def failure(error): 742 def failure(error):
711 self.showPopUp(sat_widgets.Alert(_("Error"), _("Can't get parameters (%s)") % error, ok_cb=self.removePopUp)) 743 self.showPopUp(sat_widgets.Alert(_("Error"), _("Can't get parameters (%s)") % error, ok_cb=self.removePopUp))
712 self.bridge.getParamsUI(app=C.APP_NAME, profile_key=self.profile, callback=success, errback=failure) 744 self.bridge.getParamsUI(app=C.APP_NAME, profile_key=self.current_profile, callback=success, errback=failure)
713 745
714 def onExitRequest(self, menu): 746 def onExitRequest(self, menu):
715 QuickApp.onExit(self) 747 QuickApp.onExit(self)
716 raise urwid.ExitMainLoop() 748 raise urwid.ExitMainLoop()
717 749
723 def onAboutRequest(self, menu): 755 def onAboutRequest(self, menu):
724 self.showPopUp(sat_widgets.Alert(_("About"), C.APP_NAME + " v" + self.bridge.getVersion(), ok_cb=self.removePopUp)) 756 self.showPopUp(sat_widgets.Alert(_("About"), C.APP_NAME + " v" + self.bridge.getVersion(), ok_cb=self.removePopUp))
725 757
726 #MISC CALLBACKS# 758 #MISC CALLBACKS#
727 759
728 def setStatusOnline(self, online=True, show="", statuses={}): 760 def setStatusOnline(self, online=True, show="", statuses={}, profile=C.PROF_KEY_NONE):
729 if not online or not statuses: 761 if not online or not statuses:
730 self.status_bar.setPresenceStatus(show if online else 'unavailable', '') 762 self.contact_lists[profile].status_bar.setPresenceStatus(show if online else 'unavailable', '')
731 return 763 return
732 try: 764 try:
733 self.status_bar.setPresenceStatus(show, statuses['default']) 765 self.contact_lists[profile].status_bar.setPresenceStatus(show, statuses['default'])
734 except (KeyError, TypeError): 766 except (KeyError, TypeError):
735 pass 767 pass
736 768
737 sat = PrimitivusApp() 769 sat = PrimitivusApp()
738 sat.start() 770 sat.start()