Mercurial > libervia-backend
view frontends/wix/main_window.py @ 68:9b842086d915
multiple profiles update
- Wix: new profile managed, it appear at launch in place of the contact list, and disappear once the profile is choosen
- SàT: default profile is now saved, first one is choosed is no default profile
- Bridge: new delete profile method
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 25 Feb 2010 17:09:18 +1100 |
parents | 0e50dd3a234a |
children | 8f2ed279784b |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- """ wix: a SAT frontend Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ import wx from chat import Chat from param import Param from form import Form from gateways import GatewaysManager from profile import Profile from profile_manager import ProfileManager import gobject import os.path import pdb from tools.jid import JID from logging import debug, info, error from quick_frontend.quick_chat_list import QuickChatList from quick_frontend.quick_contact_list import QuickContactList from quick_frontend.quick_app import QuickApp from quick_frontend.quick_contact_management import QuickContactManagement from cgi import escape import sys IMAGE_DIR = sys.path[0]+'/images' msgOFFLINE = "offline" msgONLINE = "online" idCONNECT,\ idDISCONNECT,\ idEXIT,\ idPARAM,\ idADD_CONTACT,\ idREMOVE_CONTACT,\ idSHOW_PROFILE,\ idFIND_GATEWAYS = range(8) const_DEFAULT_GROUP = "Unclassed" const_STATUS = [("", "Online", None), ("chat", "Free for chat", "green"), ("away", "AFK", "brown"), ("dnd", "DND", "red"), ("xa", "Away", "red")] class ChatList(QuickChatList): """This class manage the list of chat windows""" def __init__(self, host): QuickChatList.__init__(self, host) def createChat(self, target): return Chat(target, self.host) class ContactList(wx.SimpleHtmlListBox, QuickContactList): """Customized control to manage contacts.""" def __init__(self, parent, CM): wx.SimpleHtmlListBox.__init__(self, parent, -1) QuickContactList.__init__(self, CM) self.host = parent self.groups = {} #list contacts in each groups, key = group self.Bind(wx.EVT_LISTBOX, self.onSelected) self.Bind(wx.EVT_LISTBOX_DCLICK, self.onActivated) def __find_idx(self, entity, reverse=False): """Find indexes of given jid (or groups) in contact list @return: list of indexes""" result=[] for i in range(self.GetCount()): if (type(entity) == JID and type(self.GetClientData(i)) == JID and self.GetClientData(i).short == entity.short) or\ self.GetClientData(i) == entity: result.append(i) return result def replace(self, jid): debug("update %s" % jid) if not self.__find_idx(jid): self.add(jid) else: for i in self.__find_idx(jid): self.SetString(i, self.__presentItem(jid)) def disconnect(self, jid): self.remove(jid) #for now, we only show online contacts def __eraseGroup(self, group): """Erase all contacts in group @param group: group to erase @return: True if something as been erased""" erased = False indexes = self.__find_idx(group) for idx in indexes: while idx<self.GetCount()-1 and type(self.GetClientData(idx+1)) == JID: erased = True self.Delete(idx+1) return erased def __presentGroup(self, group): """Make a nice presentation for the contact groups""" html = """-- [%s] --""" % group return html def __presentItem(self, jid): """Make a nice presentation of the contact in the list.""" name = self.CM.getAttr(jid,'name') nick = self.CM.getAttr(jid,'nick') show = filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0] #show[0]==shortcut #show[1]==human readable #show[2]==color (or None) show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else "" status = self.CM.getAttr(jid,'status') or '' avatar = self.CM.getAttr(jid,'avatar') or IMAGE_DIR+'/empty_avatar.png' #XXX: yes table I know :) but wxHTML* doesn't support CSS html = """ <table border='0'> <td> <img height='64' width='64' src='%s' /> </td> <td> <b>%s</b> %s<br /> <i>%s</i> </td> </table> """ % (avatar, escape(nick or name or jid.node or jid.short), show_html, escape(status)) return html def clear_contacts(self): """Clear all the contact list""" self.Clear() def add(self, jid): """add a contact to the list""" debug ("adding %s",jid) groups = self.CM.getAttr(jid, 'groups') if not groups: idx = self.Append(self.__presentItem(jid), jid) else: for group in groups: indexes = self.__find_idx(group) gp_idx = 0 if not indexes: #this is a new group, we have to create it gp_idx = self.Append(self.__presentGroup(group), group) else: gp_idx = indexes[0] self.Insert(self.__presentItem(jid), gp_idx+1, jid) def remove(self, jid): """remove a contact from the list""" debug ("removing %s",jid) list_idx = self.__find_idx(jid) list_idx.reverse() #we me make some deletions, we have to reverse the order for i in list_idx: self.Delete(i) def onSelected(self, event): """Called when a contact is selected.""" data = self.getSelection() if type(data) == JID: event.Skip() else: group = self.GetClientData(self.GetSelection()) erased = self.__eraseGroup(group) if not erased: #the group was already erased, we can add again the contacts contacts = self.CM.getContFromGroup(group) contacts.sort() id_insert = self.GetSelection()+1 for contact in contacts: self.Insert(self.__presentItem(contact), id_insert, contact) self.SetSelection(wx.NOT_FOUND) event.Skip(False) def onActivated(self, event): """Called when a contact is clicked or activated with keyboard.""" data = self.getSelection() self.onActivatedCB(data) event.Skip() def getSelection(self): """Return the selected contact, or an empty string if there is not""" if self.GetSelection() == wx.NOT_FOUND: return None data = self.GetClientData(self.GetSelection()) if type(data) != JID: return None return data def registerActivatedCB(self, cb): """Register a callback with manage contact activation.""" self.onActivatedCB=cb class MainWindow(wx.Frame, QuickApp): """main app window""" def __init__(self): wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500)) self.CM = QuickContactManagement() #FIXME: not the best place #sizer self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) #Frame elements self.contactList = ContactList(self, self.CM) self.contactList.registerActivatedCB(self.onContactActivated) self.contactList.Hide() self.sizer.Add(self.contactList, 1, flag=wx.EXPAND) self.chat_wins=ChatList(self) self.CreateStatusBar() self.createMenus() for i in range(self.menuBar.GetMenuCount()): self.menuBar.EnableTop(i, False) #ToolBar self.tools=self.CreateToolBar() self.statusBox = wx.ComboBox(self.tools, -1, "Online", choices=[status[1] for status in const_STATUS], style=wx.CB_DROPDOWN | wx.CB_READONLY) self.tools.AddControl(self.statusBox) self.tools.AddSeparator() self.statusTxt=wx.TextCtrl(self.tools, -1, style = wx.TE_PROCESS_ENTER) self.tools.AddControl(self.statusTxt) self.Bind(wx.EVT_COMBOBOX, self.onStatusChange, self.statusBox) self.Bind(wx.EVT_TEXT_ENTER, self.onStatusChange, self.statusTxt) self.tools.Disable() #tray icon ticon = wx.Icon(IMAGE_DIR+'/crystal/tray_icon.xpm', wx.BITMAP_TYPE_XPM) self.tray_icon = wx.TaskBarIcon() self.tray_icon.SetIcon(ticon, "Wix jabber client") wx.EVT_TASKBAR_LEFT_UP(self.tray_icon, self.onTrayClick) #events self.Bind(wx.EVT_CLOSE, self.onClose, self) QuickApp.__init__(self) #self.plug_profile() #gof: #profile panel self.profile_pan = ProfileManager(self) #self.profile_pan.Hide() #gof: self.sizer.Add(self.profile_pan, 1, flag=wx.EXPAND) self.Show() def plug_profile(self, profile_key='@DEFAULT@'): """Hide profile panel then plug profile""" self.profile_pan.Hide() self.contactList.Show() self.sizer.Layout() for i in range(self.menuBar.GetMenuCount()): self.menuBar.EnableTop(i, True) super(MainWindow, self).plug_profile(profile_key) if not self.bridge.isConnected(profile_key): self.bridge.connect(profile_key) def createMenus(self): info("Creating menus") connectMenu = wx.Menu() connectMenu.Append(idCONNECT, "&Connect CTRL-c"," Connect to the server") connectMenu.Append(idDISCONNECT, "&Disconnect CTRL-d"," Disconnect from the server") connectMenu.Append(idPARAM,"&Parameters"," Configure the program") connectMenu.AppendSeparator() connectMenu.Append(idEXIT,"E&xit"," Terminate the program") contactMenu = wx.Menu() contactMenu.Append(idADD_CONTACT, "&Add contact"," Add a contact to your list") contactMenu.Append(idREMOVE_CONTACT, "&Remove contact"," Remove the selected contact from your list") contactMenu.AppendSeparator() contactMenu.Append(idSHOW_PROFILE, "&Show profile", " Show contact's profile") communicationMenu = wx.Menu() communicationMenu.Append(idFIND_GATEWAYS, "&Find Gateways"," Find gateways to legacy IM") self.menuBar = wx.MenuBar() self.menuBar.Append(connectMenu,"&General") self.menuBar.Append(contactMenu,"&Contacts") self.menuBar.Append(communicationMenu,"&Communication") self.SetMenuBar(self.menuBar) #events wx.EVT_MENU(self, idCONNECT, self.onConnectRequest) wx.EVT_MENU(self, idDISCONNECT, self.onDisconnectRequest) wx.EVT_MENU(self, idPARAM, self.onParam) wx.EVT_MENU(self, idEXIT, self.onExit) wx.EVT_MENU(self, idADD_CONTACT, self.onAddContact) wx.EVT_MENU(self, idREMOVE_CONTACT, self.onRemoveContact) wx.EVT_MENU(self, idSHOW_PROFILE, self.onShowProfile) wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways) def newMessage(self, from_jid, msg, type, to_jid, profile): QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile) def showAlert(self, message): # TODO: place this in a separate class popup=wx.PopupWindow(self) ### following code come from wxpython demo popup.SetBackgroundColour("CADET BLUE") st = wx.StaticText(popup, -1, message, pos=(10,10)) sz = st.GetBestSize() popup.SetSize( (sz.width+20, sz.height+20) ) x=(wx.DisplaySize()[0]-popup.GetSize()[0])/2 popup.SetPosition((x,0)) popup.Show() wx.CallLater(5000,popup.Destroy) def showDialog(self, message, title="", type="info"): if type == 'info': flags = wx.OK | wx.ICON_INFORMATION elif type == 'error': flags = wx.OK | wx.ICON_ERROR elif type == 'yes/no': flags = wx.YES_NO | wx.ICON_QUESTION else: flags = wx.OK | wx.ICON_INFORMATION error('unmanaged dialog type: %s', type) dlg = wx.MessageDialog(self, message, title, flags) answer = dlg.ShowModal() dlg.Destroy() return True if (answer == wx.ID_YES or answer == wx.ID_OK) else False def setStatusOnline(self, online=True): """enable/disable controls, must be called when local user online status change""" if online: self.SetStatusText(msgONLINE) self.tools.Enable() else: self.SetStatusText(msgOFFLINE) self.tools.Disable() return def askConfirmation(self, type, id, data): #TODO: refactor this in QuickApp debug ("Confirmation asked") answer_data={} if type == "FILE_TRANSFERT": debug ("File transfert confirmation asked") dlg = wx.MessageDialog(self, "The contact %s wants to send you the file %s\nDo you accept ?" % (data["from"], data["filename"]), 'File Request', wx.YES_NO | wx.ICON_QUESTION ) answer=dlg.ShowModal() if answer==wx.ID_YES: filename = wx.FileSelector("Where do you want to save the file ?", flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if filename: answer_data["dest_path"] = filename self.bridge.confirmationAnswer(id, True, answer_data) self.waitProgress(id, "File Transfer", "Copying %s" % os.path.basename(filename)) else: answer = wx.ID_NO if answer==wx.ID_NO: self.bridge.confirmationAnswer(id, False, answer_data) dlg.Destroy() elif type == "YES/NO": debug ("Yes/No confirmation asked") dlg = wx.MessageDialog(self, data["message"], 'Confirmation', wx.YES_NO | wx.ICON_QUESTION ) answer=dlg.ShowModal() if answer==wx.ID_YES: self.bridge.confirmationAnswer(id, True, {}) if answer==wx.ID_NO: self.bridge.confirmationAnswer(id, False, {}) dlg.Destroy() def actionResult(self, type, id, data): debug ("actionResult: type = [%s] id = [%s] data = [%s]" % (type, id, data)) if not id in self.current_action_ids: debug ('unknown id, ignoring') return if type == "SUPPRESS": self.current_action_ids.remove(id) elif type == "SUCCESS": self.current_action_ids.remove(id) dlg = wx.MessageDialog(self, data["message"], 'Success', wx.OK | wx.ICON_INFORMATION ) dlg.ShowModal() dlg.Destroy() elif type == "ERROR": self.current_action_ids.remove(id) dlg = wx.MessageDialog(self, data["message"], 'Error', wx.OK | wx.ICON_ERROR ) dlg.ShowModal() dlg.Destroy() elif type == "FORM": self.current_action_ids.remove(id) debug ("Form received") form=Form(self, title='Registration', target = data['target'], type = data['type'], xml_data = data['xml']) elif type == "RESULT": self.current_action_ids.remove(id) if self.current_action_ids_cb.has_key(id): callback = self.current_action_ids_cb[id] del self.current_action_ids_cb[id] callback(data) elif type == "DICT_DICT": self.current_action_ids.remove(id) if self.current_action_ids_cb.has_key(id): callback = self.current_action_ids_cb[id] del self.current_action_ids_cb[id] callback(data) else: error ("FIXME FIXME FIXME: type [%s] not implemented" % type) raise NotImplementedError def progressCB(self, id, title, message): data = self.bridge.getProgress(id) if data: if not data['position']: data['position'] = '0' if not self.pbar: #first answer, we must construct the bar self.pbar = wx.ProgressDialog(title, message, int(data['size']), None, wx.PD_SMOOTH | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME | wx.PD_REMAINING_TIME) self.pbar.finish_value = int(data['size']) self.pbar.Update(int(data['position'])) elif self.pbar: self.pbar.Update(self.pbar.finish_value) return wx.CallLater(10, self.progressCB, id, title, message) def waitProgress (self, id, title, message): self.pbar = None wx.CallLater(10, self.progressCB, id, title, message) ### events ### def onContactActivated(self, jid): debug ("onContactActivated: %s", jid) if self.chat_wins[jid.short].IsShown(): self.chat_wins[jid.short].Hide() else: self.chat_wins[jid.short].Show() def onConnectRequest(self, e): self.bridge.connect(self.profile) def onDisconnectRequest(self, e): self.bridge.disconnect(self.profile) def __updateStatus(self): show = filter(lambda x:x[1] == self.statusBox.GetValue(), const_STATUS)[0][0] status = self.statusTxt.GetValue() self.bridge.setPresence(show=show, statuses={'default':status}) #FIXME: manage multilingual statuses def onStatusChange(self, e): debug("Status change request") self.__updateStatus() def onParam(self, e): debug("Param request") param=Param(self) def onExit(self, e): self.Close() def onAddContact(self, e): debug("Add contact request") dlg = wx.TextEntryDialog( self, 'Please enter new contact JID', 'Adding a contact', 'name@server.tld') if dlg.ShowModal() == wx.ID_OK: jid=JID(dlg.GetValue()) if jid.is_valid(): self.bridge.addContact(jid.short) else: error ("'%s' is an invalid JID !", jid) #TODO: notice the user dlg.Destroy() def onRemoveContact(self, e): debug("Remove contact request") target = self.contactList.getSelection() if not target: dlg = wx.MessageDialog(self, "You haven't selected any contact !", 'Error', wx.OK | wx.ICON_ERROR ) dlg.ShowModal() dlg.Destroy() return dlg = wx.MessageDialog(self, "Are you sure you want to delete %s from your roster list ?" % target.short, 'Contact suppression', wx.YES_NO | wx.ICON_QUESTION ) if dlg.ShowModal() == wx.ID_YES: info("Unsubscribing %s presence", target.short) self.bridge.delContact(target.short) dlg.Destroy() def onShowProfile(self, e): debug("Show contact's profile request") target = self.contactList.getSelection() if not target: dlg = wx.MessageDialog(self, "You haven't selected any contact !", 'Error', wx.OK | wx.ICON_ERROR ) dlg.ShowModal() dlg.Destroy() return id = self.bridge.getCard(target.short) self.current_action_ids.add(id) self.current_action_ids_cb[id] = self.onProfileReceived def onProfileReceived(self, data): """Called when a profile is received""" debug ('Profile received: [%s]' % data) profile=Profile(self, data) def onFindGateways(self, e): debug("Find Gateways request") id = self.bridge.findGateways(self.profiles[self.profile]['whoami'].domain) self.current_action_ids.add(id) self.current_action_ids_cb[id] = self.onGatewaysFound def onGatewaysFound(self, data): """Called when SàT has found the server gateways""" target = data['__private__']['target'] del data['__private__'] gatewayManager = GatewaysManager(self, data, server=target) def onClose(self, e): info("Exiting...") e.Skip() def onTrayClick(self, e): debug("Tray Click") if self.IsShown(): self.Hide() else: self.Show() self.Raise() e.Skip()