view frontends/wix/main_window.py @ 25:53e921c8a357

new plugin: gateways plugin, and first implementation of findGateways - test menu in Wix - new actionResultExt method, for sending dictionary of dictionaries - new getNextId method, for accessing sat ids from plugins.
author Goffi <goffi@goffi.org>
date Fri, 04 Dec 2009 08:47:44 +0100
parents 925ab466c5ec
children c2b131e4e262
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
wix: a SAT frontend
Copyright (C) 2009  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
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


msgOFFLINE          = "offline"
msgONLINE           = "online"
idCONNECT           = 1
idDISCONNECT        = 2
idEXIT              = 3
idPARAM             = 4
idADD_CONTACT       = 5
idREMOVE_CONTACT    = 6
idFIND_GATEWAYS     = 7
const_DEFAULT_GROUP = "Unclassed"
const_STATUS        = {"Online":"",
                      "Want to discuss":"chat",
                      "AFK":"away",
                      "Do Not Disturb":"dnd",
                      "Away":"xa"}

class ChatList(QuickChatList):
    """This class manage the list of chat windows"""
    
    def __init__(self, host):
        QuickChatList.__init__(self, host)
    
    def createChat(self, name):
        return Chat(name, self.host)
    


class ContactList(wx.TreeCtrl, QuickContactList):
    """Customized control to manage contacts."""

    def __init__(self, parent):
        wx.TreeCtrl.__init__(self, parent, style = wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS)
        QuickContactList.__init__(self)
        self.jid_ids={}
        self.groups={}
        self.root=self.AddRoot("")
        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.onActivated, self)

        #icons
        isz = (16,16)
        il = wx.ImageList(isz[0], isz[1])
        self.icon_online = il.Add(wx.ArtProvider_GetBitmap(wx.ART_TICK_MARK, wx.ART_OTHER, isz))
        self.icon_unavailable = il.Add(wx.ArtProvider_GetBitmap(wx.ART_CROSS_MARK, wx.ART_OTHER, isz))
        self.AssignImageList(il)

        self.__addNode(const_DEFAULT_GROUP)

    def __addNode(self, label):
        """Add an item container"""
        ret=self.AppendItem(self.root, label)
        self.SetPyData(ret, "[node]")
        self.SetItemBold(ret)
        self.groups[label]=ret

    def replace(self, jid, name="", show="", status="", group=""):
        debug("status = %s show = %s",status, show)
        if not self.jid_ids.has_key(jid):
            self.add(jid, name, show, status, group)
        else:
            debug ("updating %s",jid)
            self.__presentItem(jid, name, show, status, group)

    def __presentItem(self, jid, name, show, status, group):
        """Make a nice presentation of the contact in the list."""
        id=self.jid_ids[jid]
        label= "%s [%s] \n %s" % ((name or jid), (show or "online"), status)
        self.SetItemText(id, label)

        # icon
        if not show or show=="chat":
            self.SetItemImage(id, self.icon_online)
        else:
            self.SetItemImage(id, self.icon_unavailable)

        #colour
        if not show:
            self.SetItemTextColour(id, wx.BLACK)
        elif show=="chat":
            self.SetItemTextColour(id, wx.GREEN)
        elif show=="away":
            self.SetItemTextColour(id, wx.BLUE)
        else:
            self.SetItemTextColour(id, wx.RED)

    def add(self, jid, name="", show="", status="", group=""):
        """add a contact to the list"""
        debug ("adding %s",jid)
        dest_group=group or const_DEFAULT_GROUP
        if not self.groups.has_key(dest_group):
            self.__addNode(dest_group)
        self.jid_ids[jid]=self.AppendItem(self.groups[dest_group], "")
        self.__presentItem(jid, name, show, status, group)
        self.SetPyData(self.jid_ids[jid], "[contact]"+jid)
        self.EnsureVisible(self.jid_ids[jid])
        self.Refresh() #FIXME: Best way ?

    def remove(self, jid):
        """remove a contact from the list"""
        debug ("removing %s",jid)
        self.Delete(self.jid_ids[jid])
        del self.jid_ids[jid]
        self.Refresh() #FIXME: Best way ?

    def onActivated(self, event):
        """Called when a contact is clicked or activated with keyboard."""
        if self.GetPyData(event.GetItem()).startswith("[contact]"):
            self.onActivatedCB(self.GetPyData(event.GetItem())[9:])
        else:
            event.Skip()

    def getSelection(self):
        """Return the selected contact, or an empty string if there is not"""
        data = self.GetPyData(self.GetSelection())
        if not data or not data.startswith("[contact]"):
            return ""
        return JID(data[9:])

    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="SAT Wix", size=(400,200))


        #Frame elements
        self.contactList = ContactList(self)
        self.contactList.registerActivatedCB(self.onContactActivated)
        self.chat_wins=ChatList(self)
        self.CreateStatusBar()
        self.SetStatusText(msgOFFLINE)
        self.createMenus()

        #ToolBar
        self.tools=self.CreateToolBar()
        self.statusBox =  wx.ComboBox(self.tools, -1, "Online", choices=const_STATUS.keys(),
                                      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("images/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.Show()

    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")
        communicationMenu = wx.Menu()
        communicationMenu.Append(idFIND_GATEWAYS, "&Find Gateways"," Find gateways to legacy IM")
        menuBar = wx.MenuBar()
        menuBar.Append(connectMenu,"&General")
        menuBar.Append(contactMenu,"&Contacts")
        menuBar.Append(communicationMenu,"&Communication")
        self.SetMenuBar(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, idFIND_GATEWAYS, self.onFindGateways)


    def newMessage(self, from_jid, msg, type, to_jid):
        QuickApp.newMessage(self, from_jid, msg, type, to_jid)

    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 == 'question':
            flags = wx.OK | wx.ICON_QUESTION
        else:
            flags = wx.OK | wx.ICON_INFORMATION
        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 presenceUpdate(self, jabber_id, type, show, status, priority):
        QuickApp.presenceUpdate(self, jabber_id, type, show, status, priority)

    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 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 == "DICT_DICT":
            print ("Dict of dict found as result")
        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].IsShown():
            self.chat_wins[jid].Hide()
        else:
            self.chat_wins[jid].Show()

    def onConnectRequest(self, e):
        self.bridge.connect()

    def onDisconnectRequest(self, e):
        self.bridge.disconnect()

    def __updateStatus(self):
        show = const_STATUS[self.statusBox.GetValue()]
        status =  self.statusTxt.GetValue()
        self.bridge.setPresence(show=show, status=status)

    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.ext')

        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("Unsubsribing %s presence", target.short)
            self.bridge.delContact(target.short)

        dlg.Destroy()

    def onFindGateways(self, e):
        debug("Find Gateways request")
        id = self.bridge.findGateways(self.whoami.domain)
        print "Find Gateways id=", id
    
    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()