comparison frontends/wix/main_window.py @ 72:f271fff3a713

MUC implementation: first draft /!\ the experimental muc branche of wokkel must be used - bridge: new roomJoined signal - wix: contact list widget is now in a separate file, and manage different kinds of presentation - wix: chat window now manage group chat (first draft, not working yet) - wix: constants are now in a separate class, so then can be accessible from everywhere - wix: new menu to join room (do nothing yet, except entering in a test room) - new plugin for xep 0045 (MUC), use wokkel experimental MUC branch - plugins: the profile is now given for get_handler, cause it can be used internally by a plugin (e.g.: xep-0045 plugin)
author Goffi <goffi@goffi.org>
date Sun, 21 Mar 2010 10:28:55 +1100
parents 8f2ed279784b
children 7322a41f8a8e
comparison
equal deleted inserted replaced
71:efe81b61673c 72:f271fff3a713
20 """ 20 """
21 21
22 22
23 from quick_frontend.quick_chat_list import QuickChatList 23 from quick_frontend.quick_chat_list import QuickChatList
24 from quick_frontend.quick_app import QuickApp 24 from quick_frontend.quick_app import QuickApp
25 from quick_frontend.quick_contact_list import QuickContactList
26 from quick_frontend.quick_contact_management import QuickContactManagement 25 from quick_frontend.quick_contact_management import QuickContactManagement
27 import wx 26 import wx
27 from contact_list import ContactList
28 from chat import Chat 28 from chat import Chat
29 from param import Param 29 from param import Param
30 from form import Form 30 from form import Form
31 from gateways import GatewaysManager 31 from gateways import GatewaysManager
32 from profile import Profile 32 from profile import Profile
33 from profile_manager import ProfileManager 33 from profile_manager import ProfileManager
34 import gobject 34 import gobject
35 import os.path 35 import os.path
36 import pdb 36 import pdb
37 from tools.jid import JID 37 from tools.jid import JID
38 from logging import debug, info, error 38 from logging import debug, info, warning, error
39 from cgi import escape 39 import constants
40 import sys 40
41
42 IMAGE_DIR = sys.path[0]+'/images'
43
44 msgOFFLINE = "offline"
45 msgONLINE = "online"
46 idCONNECT,\ 41 idCONNECT,\
47 idDISCONNECT,\ 42 idDISCONNECT,\
48 idEXIT,\ 43 idEXIT,\
49 idPARAM,\ 44 idPARAM,\
50 idADD_CONTACT,\ 45 idADD_CONTACT,\
51 idREMOVE_CONTACT,\ 46 idREMOVE_CONTACT,\
52 idSHOW_PROFILE,\ 47 idSHOW_PROFILE,\
53 idFIND_GATEWAYS = range(8) 48 idJOIN_ROOM,\
54 const_DEFAULT_GROUP = "Unclassed" 49 idFIND_GATEWAYS = range(9)
55 const_STATUS = [("", _("Online"), None),
56 ("chat", _("Free for chat"), "green"),
57 ("away", _("AFK"), "brown"),
58 ("dnd", _("DND"), "red"),
59 ("xa", _("Away"), "red")]
60 50
61 class ChatList(QuickChatList): 51 class ChatList(QuickChatList):
62 """This class manage the list of chat windows""" 52 """This class manage the list of chat windows"""
63 53
64 def __init__(self, host): 54 def __init__(self, host):
65 QuickChatList.__init__(self, host) 55 QuickChatList.__init__(self, host)
66 56
67 def createChat(self, target): 57 def createChat(self, target):
68 return Chat(target, self.host) 58 return Chat(target, self.host)
69 59
70
71 class ContactList(wx.SimpleHtmlListBox, QuickContactList):
72 """Customized control to manage contacts."""
73
74 def __init__(self, parent, CM):
75 wx.SimpleHtmlListBox.__init__(self, parent, -1)
76 QuickContactList.__init__(self, CM)
77 self.host = parent
78 self.groups = {} #list contacts in each groups, key = group
79 self.Bind(wx.EVT_LISTBOX, self.onSelected)
80 self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated)
81
82 def __find_idx(self, entity, reverse=False):
83 """Find indexes of given jid (or groups) in contact list
84 @return: list of indexes"""
85 result=[]
86 for i in range(self.GetCount()):
87 if (type(entity) == JID and type(self.GetClientData(i)) == JID and self.GetClientData(i).short == entity.short) or\
88 self.GetClientData(i) == entity:
89 result.append(i)
90 return result
91
92 def replace(self, jid):
93 debug(_("update %s") % jid)
94 if not self.__find_idx(jid):
95 self.add(jid)
96 else:
97 for i in self.__find_idx(jid):
98 self.SetString(i, self.__presentItem(jid))
99
100 def disconnect(self, jid):
101 self.remove(jid) #for now, we only show online contacts
102
103 def __eraseGroup(self, group):
104 """Erase all contacts in group
105 @param group: group to erase
106 @return: True if something as been erased"""
107 erased = False
108 indexes = self.__find_idx(group)
109 for idx in indexes:
110 while idx<self.GetCount()-1 and type(self.GetClientData(idx+1)) == JID:
111 erased = True
112 self.Delete(idx+1)
113 return erased
114
115
116 def __presentGroup(self, group):
117 """Make a nice presentation for the contact groups"""
118 html = """-- [%s] --""" % group
119
120 return html
121
122 def __presentItem(self, jid):
123 """Make a nice presentation of the contact in the list."""
124 name = self.CM.getAttr(jid,'name')
125 nick = self.CM.getAttr(jid,'nick')
126 show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0]
127 #show[0]==shortcut
128 #show[1]==human readable
129 #show[2]==color (or None)
130 show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
131 status = self.CM.getAttr(jid,'status') or ''
132 avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png'
133
134 #XXX: yes table I know :) but wxHTML* doesn't support CSS
135 html = """
136 <table border='0'>
137 <td>
138 <img height='64' width='64' src='%s' />
139 </td>
140 <td>
141 <b>%s</b> %s<br />
142 <i>%s</i>
143 </td>
144 </table>
145 """ % (avatar,
146 escape(nick or name or jid.node or jid.short),
147 show_html,
148 escape(status))
149
150 return html
151
152 def clear_contacts(self):
153 """Clear all the contact list"""
154 self.Clear()
155
156 def add(self, jid):
157 """add a contact to the list"""
158 debug (_("adding %s"),jid)
159 groups = self.CM.getAttr(jid, 'groups')
160 if not groups:
161 idx = self.Append(self.__presentItem(jid), jid)
162 else:
163 for group in groups:
164 indexes = self.__find_idx(group)
165 gp_idx = 0
166 if not indexes: #this is a new group, we have to create it
167 gp_idx = self.Append(self.__presentGroup(group), group)
168 else:
169 gp_idx = indexes[0]
170
171 self.Insert(self.__presentItem(jid), gp_idx+1, jid)
172
173
174
175 def remove(self, jid):
176 """remove a contact from the list"""
177 debug (_("removing %s"),jid)
178 list_idx = self.__find_idx(jid)
179 list_idx.reverse() #we me make some deletions, we have to reverse the order
180 for i in list_idx:
181 self.Delete(i)
182
183 def onSelected(self, event):
184 """Called when a contact is selected."""
185 data = self.getSelection()
186 if type(data) == JID:
187 event.Skip()
188 else:
189 group = self.GetClientData(self.GetSelection())
190 erased = self.__eraseGroup(group)
191 if not erased: #the group was already erased, we can add again the contacts
192 contacts = self.CM.getContFromGroup(group)
193 contacts.sort()
194 id_insert = self.GetSelection()+1
195 for contact in contacts:
196 self.Insert(self.__presentItem(contact), id_insert, contact)
197 self.SetSelection(wx.NOT_FOUND)
198 event.Skip(False)
199
200 def onActivated(self, event):
201 """Called when a contact is clicked or activated with keyboard."""
202 data = self.getSelection()
203 self.onActivatedCB(data)
204 event.Skip()
205
206 def getSelection(self):
207 """Return the selected contact, or an empty string if there is not"""
208 if self.GetSelection() == wx.NOT_FOUND:
209 return None
210 data = self.GetClientData(self.GetSelection())
211 if type(data) != JID:
212 return None
213 return data
214
215 def registerActivatedCB(self, cb):
216 """Register a callback with manage contact activation."""
217 self.onActivatedCB=cb
218
219 class MainWindow(wx.Frame, QuickApp): 60 class MainWindow(wx.Frame, QuickApp):
220 """main app window""" 61 """main app window"""
221 62
222 def __init__(self): 63 def __init__(self):
223 wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500)) 64 wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500))
226 #sizer 67 #sizer
227 self.sizer = wx.BoxSizer(wx.VERTICAL) 68 self.sizer = wx.BoxSizer(wx.VERTICAL)
228 self.SetSizer(self.sizer) 69 self.SetSizer(self.sizer)
229 70
230 #Frame elements 71 #Frame elements
231 self.contactList = ContactList(self, self.CM) 72 self.contactList = ContactList(self, self)
232 self.contactList.registerActivatedCB(self.onContactActivated) 73 self.contactList.registerActivatedCB(self.onContactActivated)
233 self.contactList.Hide() 74 self.contactList.Hide()
234 self.sizer.Add(self.contactList, 1, flag=wx.EXPAND) 75 self.sizer.Add(self.contactList, 1, flag=wx.EXPAND)
235 76
236 self.chat_wins=ChatList(self) 77 self.chat_wins=ChatList(self)
294 contactMenu.Append(idADD_CONTACT, _("&Add contact"),_(" Add a contact to your list")) 135 contactMenu.Append(idADD_CONTACT, _("&Add contact"),_(" Add a contact to your list"))
295 contactMenu.Append(idREMOVE_CONTACT, _("&Remove contact"),_(" Remove the selected contact from your list")) 136 contactMenu.Append(idREMOVE_CONTACT, _("&Remove contact"),_(" Remove the selected contact from your list"))
296 contactMenu.AppendSeparator() 137 contactMenu.AppendSeparator()
297 contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile")) 138 contactMenu.Append(idSHOW_PROFILE, _("&Show profile"), _(" Show contact's profile"))
298 communicationMenu = wx.Menu() 139 communicationMenu = wx.Menu()
140 communicationMenu.Append(idJOIN_ROOM, _("&Join Room"),_(" Join a Multi-User Chat room"))
299 communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM")) 141 communicationMenu.Append(idFIND_GATEWAYS, _("&Find Gateways"),_(" Find gateways to legacy IM"))
300 self.menuBar = wx.MenuBar() 142 self.menuBar = wx.MenuBar()
301 self.menuBar.Append(connectMenu,_("&General")) 143 self.menuBar.Append(connectMenu,_("&General"))
302 self.menuBar.Append(contactMenu,_("&Contacts")) 144 self.menuBar.Append(contactMenu,_("&Contacts"))
303 self.menuBar.Append(communicationMenu,_("&Communication")) 145 self.menuBar.Append(communicationMenu,_("&Communication"))
309 wx.EVT_MENU(self, idPARAM, self.onParam) 151 wx.EVT_MENU(self, idPARAM, self.onParam)
310 wx.EVT_MENU(self, idEXIT, self.onExit) 152 wx.EVT_MENU(self, idEXIT, self.onExit)
311 wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact) 153 wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact)
312 wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact) 154 wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact)
313 wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile) 155 wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile)
156 wx.EVT_MENU(self, idJOIN_ROOM, self.onJoinRoom)
314 wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways) 157 wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways)
315 158
316 159
317 def newMessage(self, from_jid, msg, type, to_jid, profile): 160 def newMessage(self, from_jid, msg, type, to_jid, profile):
318 QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile) 161 QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
162
163 def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
164 super(MainWindow, self).roomJoined(room_id, room_service, room_nicks, user_nick, profile)
165 self.chat_wins[room_id+'@'+room_service].setType("group")
166 self.chat_wins[room_id+'@'+room_service].setPresents([user_nick]+room_nicks)
319 167
320 def showAlert(self, message): 168 def showAlert(self, message):
321 # TODO: place this in a separate class 169 # TODO: place this in a separate class
322 popup=wx.PopupWindow(self) 170 popup=wx.PopupWindow(self)
323 ### following code come from wxpython demo 171 ### following code come from wxpython demo
550 def onProfileReceived(self, data): 398 def onProfileReceived(self, data):
551 """Called when a profile is received""" 399 """Called when a profile is received"""
552 debug (_('Profile received: [%s]') % data) 400 debug (_('Profile received: [%s]') % data)
553 profile=Profile(self, data) 401 profile=Profile(self, data)
554 402
403 def onJoinRoom(self, e):
404 warning('FIXME: temporary menu, must be improved')
405 self.bridge.joinMUC("conference.necton2.int", "test", "Goffi \o/", self.profile)
555 406
556 def onFindGateways(self, e): 407 def onFindGateways(self, e):
557 debug(_("Find Gateways request")) 408 debug(_("Find Gateways request"))
558 id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain) 409 id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain)
559 self.current_action_ids.add(id) 410 self.current_action_ids.add(id)