Mercurial > libervia-backend
comparison frontends/wix/main_window.py @ 51:8c67ea98ab91
frontend improved to take into account new SàT features
- quick_frontend: better use of contact management, it now manages nicks, avatars, and connected status
- quick_frontend: disconnect and remove are now 2 separate methods for contact list
- wix: new contact list using HTML items, and showing avatars. Groups are not showed for now
- wix: contact status now use tuples, to keep order, human readable status and color of contact
- wix: contact list is updated when avatar or nick is found
- wix: fixed 'question' dialog, which is renamed in 'yes/no'
- wix: action result are now ignored for unkwnown id
- sortilege refactored to work again
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 07 Jan 2010 00:17:27 +1100 |
parents | 874de3020e1c |
children | 6455fb62ff83 |
comparison
equal
deleted
inserted
replaced
50:daa1f01a5332 | 51:8c67ea98ab91 |
---|---|
32 from tools.jid import JID | 32 from tools.jid import JID |
33 from logging import debug, info, error | 33 from logging import debug, info, error |
34 from quick_frontend.quick_chat_list import QuickChatList | 34 from quick_frontend.quick_chat_list import QuickChatList |
35 from quick_frontend.quick_contact_list import QuickContactList | 35 from quick_frontend.quick_contact_list import QuickContactList |
36 from quick_frontend.quick_app import QuickApp | 36 from quick_frontend.quick_app import QuickApp |
37 | 37 from quick_frontend.quick_contact_management import QuickContactManagement |
38 from cgi import escape | |
39 import sys | |
40 | |
41 IMAGE_DIR = sys.path[0]+'/images' | |
38 | 42 |
39 msgOFFLINE = "offline" | 43 msgOFFLINE = "offline" |
40 msgONLINE = "online" | 44 msgONLINE = "online" |
41 idCONNECT = 1 | 45 idCONNECT,\ |
42 idDISCONNECT = 2 | 46 idDISCONNECT,\ |
43 idEXIT = 3 | 47 idEXIT,\ |
44 idPARAM = 4 | 48 idPARAM,\ |
45 idADD_CONTACT = 5 | 49 idADD_CONTACT,\ |
46 idREMOVE_CONTACT = 6 | 50 idREMOVE_CONTACT,\ |
47 idSHOW_PROFILE = 7 | 51 idSHOW_PROFILE,\ |
48 idFIND_GATEWAYS = 8 | 52 idFIND_GATEWAYS = range(8) |
49 const_DEFAULT_GROUP = "Unclassed" | 53 const_DEFAULT_GROUP = "Unclassed" |
50 const_STATUS = {"Online":"", | 54 const_STATUS = [("", "Online", None), |
51 "Want to discuss":"chat", | 55 ("chat", "Free for chat", "green"), |
52 "AFK":"away", | 56 ("away", "AFK", "brown"), |
53 "Do Not Disturb":"dnd", | 57 ("dnd", "DND", "red"), |
54 "Away":"xa"} | 58 ("xa", "Away", "red")] |
55 | 59 |
56 class ChatList(QuickChatList): | 60 class ChatList(QuickChatList): |
57 """This class manage the list of chat windows""" | 61 """This class manage the list of chat windows""" |
58 | 62 |
59 def __init__(self, host): | 63 def __init__(self, host): |
60 QuickChatList.__init__(self, host) | 64 QuickChatList.__init__(self, host) |
61 | 65 |
62 def createChat(self, name): | 66 def createChat(self, target): |
63 return Chat(name, self.host) | 67 return Chat(target, self.host) |
64 | 68 |
65 | 69 |
66 | 70 class ContactList(wx.SimpleHtmlListBox, QuickContactList): |
67 class ContactList(wx.TreeCtrl, QuickContactList): | |
68 """Customized control to manage contacts.""" | 71 """Customized control to manage contacts.""" |
69 | 72 |
70 def __init__(self, parent): | 73 def __init__(self, parent, CM): |
71 wx.TreeCtrl.__init__(self, parent, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS) | 74 wx.SimpleHtmlListBox.__init__(self, parent, -1) |
72 QuickContactList.__init__(self) | 75 QuickContactList.__init__(self, CM) |
73 self.jid_ids={} | 76 self.host = parent |
74 self.groups={} | 77 self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated) |
75 self.root=self.AddRoot("") | 78 |
76 self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.onActivated, self) | 79 def __find_idx(self, jid, reverse=False): |
77 | 80 """Find indexes of given jid in contact list |
78 #icons | 81 @return: list of indexes""" |
79 isz = (16,16) | 82 result=[] |
80 il = wx.ImageList(isz[0], isz[1]) | 83 for i in range(self.GetCount()): |
81 self.icon_online = il.Add(wx.ArtProvider_GetBitmap(wx.ART_TICK_MARK, wx.ART_OTHER, isz)) | 84 if self.GetClientData(i).short == jid.short: |
82 self.icon_unavailable = il.Add(wx.ArtProvider_GetBitmap(wx.ART_CROSS_MARK, wx.ART_OTHER, isz)) | 85 result.append(i) |
83 self.AssignImageList(il) | 86 return result |
84 | 87 |
85 self.__addNode(const_DEFAULT_GROUP) | 88 def replace(self, jid): |
86 | 89 debug("update %s" % jid) |
87 def __addNode(self, label): | 90 if not self.__find_idx(jid): |
88 """Add an item container""" | 91 self.add(jid) |
89 ret=self.AppendItem(self.root, label) | |
90 self.SetPyData(ret, "[node]") | |
91 self.SetItemBold(ret) | |
92 self.groups[label]=ret | |
93 | |
94 def replace(self, jid, name="", show="", status="", group=""): | |
95 debug("status = %s show = %s",status, show) | |
96 if not self.jid_ids.has_key(jid): | |
97 self.add(jid, name, show, status, group) | |
98 else: | 92 else: |
99 debug ("updating %s",jid) | 93 for i in self.__find_idx(jid): |
100 self.__presentItem(jid, name, show, status, group) | 94 self.SetString(i, self.__presentItem(jid)) |
101 | 95 |
102 def __presentItem(self, jid, name, show, status, group): | 96 def disconnect(self, jid): |
97 self.remove(jid) #for now, we only show online contacts | |
98 | |
99 def __presentItem(self, jid): | |
103 """Make a nice presentation of the contact in the list.""" | 100 """Make a nice presentation of the contact in the list.""" |
104 id=self.jid_ids[jid] | 101 name = self.CM.getAttr(jid,'name') |
105 label= "%s [%s] \n %s" % ((name or jid), (show or "online"), status) | 102 nick = self.CM.getAttr(jid,'nick') |
106 self.SetItemText(id, label) | 103 show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0] |
107 | 104 #show[0]==shortcut |
108 # icon | 105 #show[1]==human readable |
109 if not show or show=="chat": | 106 #show[2]==color (or None) |
110 self.SetItemImage(id, self.icon_online) | 107 show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else "" |
111 else: | 108 status = self.CM.getAttr(jid,'status') or '' |
112 self.SetItemImage(id, self.icon_unavailable) | 109 avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png' |
113 | 110 |
114 #colour | 111 #XXX: yes table I know :) but wxHTML* doesn't support CSS |
115 if not show: | 112 html = """ |
116 self.SetItemTextColour(id, wx.BLACK) | 113 <table border='0'> |
117 elif show=="chat": | 114 <td> |
118 self.SetItemTextColour(id, wx.GREEN) | 115 <img height='64' width='64' src='%s' /> |
119 elif show=="away": | 116 </td> |
120 self.SetItemTextColour(id, wx.BLUE) | 117 <td> |
121 else: | 118 <b>%s</b> %s<br /> |
122 self.SetItemTextColour(id, wx.RED) | 119 <i>%s</i> |
123 | 120 </td> |
124 def add(self, jid, name="", show="", status="", group=""): | 121 </table> |
122 """ % (avatar, | |
123 escape(nick or name or jid.node or jid.short), | |
124 show_html, | |
125 escape(status)) | |
126 | |
127 return html | |
128 | |
129 def add(self, jid): | |
125 """add a contact to the list""" | 130 """add a contact to the list""" |
126 debug ("adding %s",jid) | 131 debug ("adding %s",jid) |
127 dest_group=group or const_DEFAULT_GROUP | 132 idx = self.Append(self.__presentItem(jid)) |
128 if not self.groups.has_key(dest_group): | 133 |
129 self.__addNode(dest_group) | 134 self.SetClientData(idx, jid) |
130 self.jid_ids[jid]=self.AppendItem(self.groups[dest_group], "") | |
131 self.__presentItem(jid, name, show, status, group) | |
132 self.SetPyData(self.jid_ids[jid], "[contact]"+jid) | |
133 self.EnsureVisible(self.jid_ids[jid]) | |
134 self.Refresh() #FIXME: Best way ? | |
135 | 135 |
136 def remove(self, jid): | 136 def remove(self, jid): |
137 """remove a contact from the list""" | 137 """remove a contact from the list""" |
138 debug ("removing %s",jid) | 138 debug ("removing %s",jid) |
139 self.Delete(self.jid_ids[jid]) | 139 list_idx = self.__find_idx(jid) |
140 del self.jid_ids[jid] | 140 list_idx.reverse() #we me make some deletions, we have to reverse the order |
141 self.Refresh() #FIXME: Best way ? | 141 for i in list_idx: |
142 self.Delete(i) | |
142 | 143 |
143 def onActivated(self, event): | 144 def onActivated(self, event): |
144 """Called when a contact is clicked or activated with keyboard.""" | 145 """Called when a contact is clicked or activated with keyboard.""" |
145 if self.GetPyData(event.GetItem()).startswith("[contact]"): | 146 data = self.getSelection() |
146 self.onActivatedCB(self.GetPyData(event.GetItem())[9:]) | 147 self.onActivatedCB(data) |
147 else: | 148 event.Skip() |
148 event.Skip() | |
149 | 149 |
150 def getSelection(self): | 150 def getSelection(self): |
151 """Return the selected contact, or an empty string if there is not""" | 151 """Return the selected contact, or an empty string if there is not""" |
152 data = self.GetPyData(self.GetSelection()) | 152 if self.GetSelection() == wx.NOT_FOUND: |
153 if not data or not data.startswith("[contact]"): | 153 return "" #FIXME: gof: à améliorer |
154 return "" | 154 data = self.GetClientData(self.GetSelection()) |
155 return JID(data[9:]) | 155 return data |
156 | 156 |
157 def registerActivatedCB(self, cb): | 157 def registerActivatedCB(self, cb): |
158 """Register a callback with manage contact activation.""" | 158 """Register a callback with manage contact activation.""" |
159 self.onActivatedCB=cb | 159 self.onActivatedCB=cb |
160 | 160 |
161 class MainWindow(wx.Frame, QuickApp): | 161 class MainWindow(wx.Frame, QuickApp): |
162 """main app window""" | 162 """main app window""" |
163 | 163 |
164 def __init__(self): | 164 def __init__(self): |
165 wx.Frame.__init__(self,None, title="SAT Wix", size=(400,200)) | 165 wx.Frame.__init__(self,None, title="SAT Wix", size=(400,200)) |
166 self.CM = QuickContactManagement() #gof: | |
166 | 167 |
167 | 168 |
168 #Frame elements | 169 #Frame elements |
169 self.contactList = ContactList(self) | 170 self.contactList = ContactList(self, self.CM) |
170 self.contactList.registerActivatedCB(self.onContactActivated) | 171 self.contactList.registerActivatedCB(self.onContactActivated) |
171 self.chat_wins=ChatList(self) | 172 self.chat_wins=ChatList(self) |
172 self.CreateStatusBar() | 173 self.CreateStatusBar() |
173 self.SetStatusText(msgOFFLINE) | 174 self.SetStatusText(msgOFFLINE) |
174 self.createMenus() | 175 self.createMenus() |
175 | 176 |
176 #ToolBar | 177 #ToolBar |
177 self.tools=self.CreateToolBar() | 178 self.tools=self.CreateToolBar() |
178 self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=const_STATUS.keys(), | 179 self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=[status[1] for status in const_STATUS], |
179 style=wx.CB_DROPDOWN | wx.CB_READONLY) | 180 style=wx.CB_DROPDOWN | wx.CB_READONLY) |
180 self.tools.AddControl(self.statusBox) | 181 self.tools.AddControl(self.statusBox) |
181 self.tools.AddSeparator() | 182 self.tools.AddSeparator() |
182 self.statusTxt=wx.TextCtrl(self.tools, -1, style = wx.TE_PROCESS_ENTER) | 183 self.statusTxt=wx.TextCtrl(self.tools, -1, style = wx.TE_PROCESS_ENTER) |
183 self.tools.AddControl(self.statusTxt) | 184 self.tools.AddControl(self.statusTxt) |
184 self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox) | 185 self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox) |
185 self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt) | 186 self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt) |
186 self.tools.Disable() | 187 self.tools.Disable() |
187 | 188 |
188 #tray icon | 189 #tray icon |
189 ticon = wx.Icon("images/tray_icon.xpm", wx.BITMAP_TYPE_XPM) | 190 ticon = wx.Icon(IMAGE_DIR+'/crystal/tray_icon.xpm', wx.BITMAP_TYPE_XPM) |
190 self.tray_icon = wx.TaskBarIcon() | 191 self.tray_icon = wx.TaskBarIcon() |
191 self.tray_icon.SetIcon(ticon, "Wix jabber client") | 192 self.tray_icon.SetIcon(ticon, "Wix jabber client") |
192 wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick) | 193 wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick) |
193 | 194 |
194 | 195 |
250 def showDialog(self, message, title="", type="info"): | 251 def showDialog(self, message, title="", type="info"): |
251 if type == 'info': | 252 if type == 'info': |
252 flags = wx.OK | wx.ICON_INFORMATION | 253 flags = wx.OK | wx.ICON_INFORMATION |
253 elif type == 'error': | 254 elif type == 'error': |
254 flags = wx.OK | wx.ICON_ERROR | 255 flags = wx.OK | wx.ICON_ERROR |
255 elif type == 'question': | 256 elif type == 'yes/no': |
256 flags = wx.OK | wx.ICON_QUESTION | 257 flags = wx.YES_NO | wx.ICON_QUESTION |
257 else: | 258 else: |
258 flags = wx.OK | wx.ICON_INFORMATION | 259 flags = wx.OK | wx.ICON_INFORMATION |
260 error('unmanaged dialog type: %s', type) | |
259 dlg = wx.MessageDialog(self, message, title, flags) | 261 dlg = wx.MessageDialog(self, message, title, flags) |
260 answer = dlg.ShowModal() | 262 answer = dlg.ShowModal() |
261 dlg.Destroy() | 263 dlg.Destroy() |
262 return True if (answer == wx.ID_YES or answer == wx.ID_OK) else False | 264 return True if (answer == wx.ID_YES or answer == wx.ID_OK) else False |
263 | 265 |
270 self.SetStatusText(msgOFFLINE) | 272 self.SetStatusText(msgOFFLINE) |
271 self.tools.Disable() | 273 self.tools.Disable() |
272 return | 274 return |
273 | 275 |
274 | 276 |
275 def presenceUpdate(self, jabber_id, type, show, status, priority): | 277 def presenceUpdate(self, jabber_id, show, priority, statuses): |
276 QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority) | 278 QuickApp.presenceUpdate(self, jabber_id, show, priority, statuses) |
277 | 279 |
278 def askConfirmation(self, type, id, data): | 280 def askConfirmation(self, type, id, data): |
279 #TODO: refactor this in QuickApp | 281 #TODO: refactor this in QuickApp |
280 debug ("Confirmation asked") | 282 debug ("Confirmation asked") |
281 answer_data={} | 283 answer_data={} |
313 | 315 |
314 dlg.Destroy() | 316 dlg.Destroy() |
315 | 317 |
316 def actionResult(self, type, id, data): | 318 def actionResult(self, type, id, data): |
317 debug ("actionResult: type = [%s] id = [%s] data = [%s]" % (type, id, data)) | 319 debug ("actionResult: type = [%s] id = [%s] data = [%s]" % (type, id, data)) |
320 if not id in self.current_action_ids: | |
321 debug ('unknown id, ignoring') | |
322 return | |
318 if type == "SUPPRESS": | 323 if type == "SUPPRESS": |
319 self.current_action_ids.remove(id) | 324 self.current_action_ids.remove(id) |
320 elif type == "SUCCESS": | 325 elif type == "SUCCESS": |
321 self.current_action_ids.remove(id) | 326 self.current_action_ids.remove(id) |
322 dlg = wx.MessageDialog(self, data["message"], | 327 dlg = wx.MessageDialog(self, data["message"], |
347 self.current_action_ids.remove(id) | 352 self.current_action_ids.remove(id) |
348 if self.current_action_ids_cb.has_key(id): | 353 if self.current_action_ids_cb.has_key(id): |
349 callback = self.current_action_ids_cb[id] | 354 callback = self.current_action_ids_cb[id] |
350 del self.current_action_ids_cb[id] | 355 del self.current_action_ids_cb[id] |
351 callback(data) | 356 callback(data) |
352 print ("Dict of dict found as result") | |
353 else: | 357 else: |
354 error ("FIXME FIXME FIXME: type [%s] not implemented" % type) | 358 error ("FIXME FIXME FIXME: type [%s] not implemented" % type) |
355 raise NotImplementedError | 359 raise NotImplementedError |
356 | 360 |
357 | 361 |
382 | 386 |
383 ### events ### | 387 ### events ### |
384 | 388 |
385 def onContactActivated(self, jid): | 389 def onContactActivated(self, jid): |
386 debug ("onContactActivated: %s", jid) | 390 debug ("onContactActivated: %s", jid) |
387 if self.chat_wins[jid].IsShown(): | 391 if self.chat_wins[jid.short].IsShown(): |
388 self.chat_wins[jid].Hide() | 392 self.chat_wins[jid.short].Hide() |
389 else: | 393 else: |
390 self.chat_wins[jid].Show() | 394 self.chat_wins[jid.short].Show() |
391 | 395 |
392 def onConnectRequest(self, e): | 396 def onConnectRequest(self, e): |
393 self.bridge.connect() | 397 self.bridge.connect() |
394 | 398 |
395 def onDisconnectRequest(self, e): | 399 def onDisconnectRequest(self, e): |
396 self.bridge.disconnect() | 400 self.bridge.disconnect() |
397 | 401 |
398 def __updateStatus(self): | 402 def __updateStatus(self): |
399 show = const_STATUS[self.statusBox.GetValue()] | 403 show = filter(lambda x:x[1] == self.statusBox.GetValue(), const_STATUS)[0][0] |
400 status = self.statusTxt.GetValue() | 404 status = self.statusTxt.GetValue() |
401 self.bridge.setPresence(show=show, status=status) | 405 self.bridge.setPresence(show=show, statuses={'default':status}) #FIXME: manage multilingual statuses |
402 | 406 |
403 def onStatusChange(self, e): | 407 def onStatusChange(self, e): |
404 debug("Status change request") | 408 debug("Status change request") |
405 self.__updateStatus() | 409 self.__updateStatus() |
406 | 410 |
443 'Contact suppression', | 447 'Contact suppression', |
444 wx.YES_NO | wx.ICON_QUESTION | 448 wx.YES_NO | wx.ICON_QUESTION |
445 ) | 449 ) |
446 | 450 |
447 if dlg.ShowModal() == wx.ID_YES: | 451 if dlg.ShowModal() == wx.ID_YES: |
448 info("Unsubsribing %s presence", target.short) | 452 info("Unsubscribing %s presence", target.short) |
449 self.bridge.delContact(target.short) | 453 self.bridge.delContact(target.short) |
450 | 454 |
451 dlg.Destroy() | 455 dlg.Destroy() |
452 | 456 |
453 def onShowProfile(self, e): | 457 def onShowProfile(self, e): |
474 def onFindGateways(self, e): | 478 def onFindGateways(self, e): |
475 debug("Find Gateways request") | 479 debug("Find Gateways request") |
476 id = self.bridge.findGateways(self.whoami.domain) | 480 id = self.bridge.findGateways(self.whoami.domain) |
477 self.current_action_ids.add(id) | 481 self.current_action_ids.add(id) |
478 self.current_action_ids_cb[id] = self.onGatewaysFound | 482 self.current_action_ids_cb[id] = self.onGatewaysFound |
479 print "Find Gateways id=", id | |
480 | 483 |
481 def onGatewaysFound(self, data): | 484 def onGatewaysFound(self, data): |
482 """Called when SàT has found the server gateways""" | 485 """Called when SàT has found the server gateways""" |
483 target = data['__private__']['target'] | 486 target = data['__private__']['target'] |
484 del data['__private__'] | 487 del data['__private__'] |