summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/pygui/application.py
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/pygui/application.py')
-rw-r--r--pjsip-apps/src/pygui/application.py510
1 files changed, 510 insertions, 0 deletions
diff --git a/pjsip-apps/src/pygui/application.py b/pjsip-apps/src/pygui/application.py
new file mode 100644
index 00000000..0dd5fa50
--- /dev/null
+++ b/pjsip-apps/src/pygui/application.py
@@ -0,0 +1,510 @@
+# $Id$
+#
+# pjsua Python GUI Demo
+#
+# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+import sys
+if sys.version_info[0] >= 3: # Python 3
+ import tkinter as tk
+ from tkinter import ttk
+ from tkinter import messagebox as msgbox
+else:
+ import Tkinter as tk
+ import tkMessageBox as msgbox
+ import ttk
+
+import pjsua2 as pj
+import log
+import accountsetting
+import account
+import buddy
+import endpoint
+import settings
+
+import os
+import traceback
+
+
+class Application(ttk.Frame):
+ """
+ The Application main frame.
+ """
+ def __init__(self):
+ ttk.Frame.__init__(self, name='application', width=300, height=500)
+ self.pack(expand='yes', fill='both')
+ self.master.title('pjsua2 Demo')
+ self.master.geometry('500x500+100+100')
+
+ # Logger
+ self.logger = log.Logger()
+
+ # Accounts
+ self.accList = []
+
+ # GUI variables
+ self.showLogWindow = tk.IntVar(value=0)
+ self.quitting = False
+
+ # Construct GUI
+ self._createWidgets()
+
+ # Log window
+ self.logWindow = log.LogWindow(self)
+ self._onMenuShowHideLogWindow()
+
+ # Instantiate endpoint
+ self.ep = endpoint.Endpoint()
+ self.ep.libCreate()
+
+ # Default config
+ self.appConfig = settings.AppConfig()
+ self.appConfig.epConfig.uaConfig.threadCnt = 0;
+ self.appConfig.epConfig.uaConfig.mainThreadOnly = True
+ self.appConfig.epConfig.logConfig.writer = self.logger
+ self.appConfig.epConfig.logConfig.filename = "pygui.log"
+ self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND
+ self.appConfig.epConfig.logConfig.level = 5
+ self.appConfig.epConfig.logConfig.consoleLevel = 5
+
+ def saveConfig(self, filename='pygui.js'):
+ # Save disabled accounts since they are not listed in self.accList
+ disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled]
+ self.appConfig.accounts = []
+
+ # Get account configs from active accounts
+ for acc in self.accList:
+ acfg = settings.AccConfig()
+ acfg.enabled = True
+ acfg.config = acc.cfg
+ for bud in acc.buddyList:
+ acfg.buddyConfigs.append(bud.cfg)
+ self.appConfig.accounts.append(acfg)
+
+ # Put back disabled accounts
+ self.appConfig.accounts.extend(disabled_accs)
+ # Save
+ self.appConfig.saveFile(filename)
+
+ def start(self, cfg_file='pygui.js'):
+ # Load config
+ if cfg_file and os.path.exists(cfg_file):
+ self.appConfig.loadFile(cfg_file)
+
+ self.appConfig.epConfig.uaConfig.threadCnt = 0;
+ self.appConfig.epConfig.uaConfig.mainThreadOnly = True
+ self.appConfig.epConfig.logConfig.writer = self.logger
+ self.appConfig.epConfig.logConfig.level = 5
+ self.appConfig.epConfig.logConfig.consoleLevel = 5
+
+ # Initialize library
+ self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full;
+ self.ep.libInit(self.appConfig.epConfig)
+ self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full)
+
+ # Create transports
+ if self.appConfig.udp.enabled:
+ self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config)
+ if self.appConfig.tcp.enabled:
+ self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config)
+ if self.appConfig.tls.enabled:
+ self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config)
+
+ # Add accounts
+ for cfg in self.appConfig.accounts:
+ if cfg.enabled:
+ self._createAcc(cfg.config)
+ acc = self.accList[-1]
+ for buddy_cfg in cfg.buddyConfigs:
+ self._createBuddy(acc, buddy_cfg)
+
+ # Start library
+ self.ep.libStart()
+
+ # Start polling
+ self._onTimer()
+
+ def updateAccount(self, acc):
+ if acc.deleting:
+ return # ignore
+ iid = str(acc.randId)
+ text = acc.cfg.idUri
+ status = acc.statusText()
+
+ values = (status,)
+ if self.tv.exists(iid):
+ self.tv.item(iid, text=text, values=values)
+ else:
+ self.tv.insert('', 'end', iid, open=True, text=text, values=values)
+
+ def updateBuddy(self, bud):
+ iid = 'buddy' + str(bud.randId)
+ text = bud.cfg.uri
+ status = bud.statusText()
+
+ values = (status,)
+ if self.tv.exists(iid):
+ self.tv.item(iid, text=text, values=values)
+ else:
+ self.tv.insert(str(bud.account.randId), 'end', iid, open=True, text=text, values=values)
+
+ def _createAcc(self, acc_cfg):
+ acc = account.Account(self)
+ acc.cfg = acc_cfg
+ self.accList.append(acc)
+ self.updateAccount(acc)
+ acc.create(acc.cfg)
+ acc.cfgChanged = False
+ self.updateAccount(acc)
+
+ def _createBuddy(self, acc, buddy_cfg):
+ bud = buddy.Buddy(self)
+ bud.cfg = buddy_cfg
+ bud.account = acc
+ bud.create(acc, bud.cfg)
+ self.updateBuddy(bud)
+ acc.buddyList.append(bud)
+
+ def _createWidgets(self):
+ self._createAppMenu()
+
+ # Main pane, a Treeview
+ self.tv = ttk.Treeview(self, columns=('Status'), show='tree')
+ self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5)
+
+ self._createContextMenu()
+
+ # Handle close event
+ self.master.protocol("WM_DELETE_WINDOW", self._onClose)
+
+ def _createAppMenu(self):
+ # Main menu bar
+ top = self.winfo_toplevel()
+ self.menubar = tk.Menu()
+ top.configure(menu=self.menubar)
+
+ # File menu
+ file_menu = tk.Menu(self.menubar, tearoff=False)
+ self.menubar.add_cascade(label="File", menu=file_menu)
+ file_menu.add_command(label="Add account..", command=self._onMenuAddAccount)
+ file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow)
+ file_menu.add_separator()
+ file_menu.add_command(label="Settings...", command=self._onMenuSettings)
+ file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings)
+ file_menu.add_separator()
+ file_menu.add_command(label="Quit", command=self._onMenuQuit)
+
+ # Window menu
+ self.window_menu = tk.Menu(self.menubar, tearoff=False)
+ self.menubar.add_cascade(label="Window", menu=self.window_menu)
+
+ # Help menu
+ help_menu = tk.Menu(self.menubar, tearoff=False)
+ self.menubar.add_cascade(label="Help", menu=help_menu)
+ help_menu.add_command(label="About", underline=2, command=self._onMenuAbout)
+
+ def _showChatWindow(self, chat_inst):
+ chat_inst.showWindow()
+
+ def updateWindowMenu(self):
+ # Chat windows
+ self.window_menu.delete(0, tk.END)
+ for acc in self.accList:
+ for c in acc.chatList:
+ cmd = lambda arg=c: self._showChatWindow(arg)
+ self.window_menu.add_command(label=c.title, command=cmd)
+
+ def _createContextMenu(self):
+ top = self.winfo_toplevel()
+
+ # Create Account context menu
+ self.accMenu = tk.Menu(top, tearoff=False)
+ # Labels, must match with _onAccContextMenu()
+ labels = ['Unregister', 'Reregister', 'Add buddy...', '-',
+ 'Online', 'Invisible', 'Away', 'Busy', '-',
+ 'Settings...', '-',
+ 'Delete...']
+ for label in labels:
+ if label=='-':
+ self.accMenu.add_separator()
+ else:
+ cmd = lambda arg=label: self._onAccContextMenu(arg)
+ self.accMenu.add_command(label=label, command=cmd)
+
+ # Create Buddy context menu
+ # Labels, must match with _onBuddyContextMenu()
+ self.buddyMenu = tk.Menu(top, tearoff=False)
+ labels = ['Audio call', 'Send instant message', '-',
+ 'Subscribe', 'Unsubscribe', '-',
+ 'Settings...', '-',
+ 'Delete...']
+
+ for label in labels:
+ if label=='-':
+ self.buddyMenu.add_separator()
+ else:
+ cmd = lambda arg=label: self._onBuddyContextMenu(arg)
+ self.buddyMenu.add_command(label=label, command=cmd)
+
+ if (top.tk.call('tk', 'windowingsystem')=='aqua'):
+ self.tv.bind('<2>', self._onTvRightClick)
+ self.tv.bind('<Control-1>', self._onTvRightClick)
+ else:
+ self.tv.bind('<3>', self._onTvRightClick)
+ self.tv.bind('<Double-Button-1>', self._onTvDoubleClick)
+
+ def _getSelectedAccount(self):
+ items = self.tv.selection()
+ if not items:
+ return None
+ try:
+ iid = int(items[0])
+ except:
+ return None
+ accs = [acc for acc in self.accList if acc.randId==iid]
+ if not accs:
+ return None
+ return accs[0]
+
+ def _getSelectedBuddy(self):
+ items = self.tv.selection()
+ if not items:
+ return None
+ try:
+ iid = int(items[0][5:])
+ iid_parent = int(self.tv.parent(items[0]))
+ except:
+ return None
+
+ accs = [acc for acc in self.accList if acc.randId==iid_parent]
+ if not accs:
+ return None
+
+ buds = [b for b in accs[0].buddyList if b.randId==iid]
+ if not buds:
+ return None
+
+ return buds[0]
+
+ def _onTvRightClick(self, event):
+ iid = self.tv.identify_row(event.y)
+ #iid = self.tv.identify('item', event.x, event.y)
+ if iid:
+ self.tv.selection_set( (iid,) )
+ acc = self._getSelectedAccount()
+ if acc:
+ self.accMenu.post(event.x_root, event.y_root)
+ else:
+ # A buddy is selected
+ self.buddyMenu.post(event.x_root, event.y_root)
+
+ def _onTvDoubleClick(self, event):
+ iid = self.tv.identify_row(event.y)
+ if iid:
+ self.tv.selection_set( (iid,) )
+ acc = self._getSelectedAccount()
+ if acc:
+ self.cfgChanged = False
+ dlg = accountsetting.Dialog(self.master, acc.cfg)
+ if dlg.doModal():
+ self.updateAccount(acc)
+ acc.modify(acc.cfg)
+ else:
+ bud = self._getSelectedBuddy()
+ acc = bud.account
+ chat = acc.findChat(bud.cfg.uri)
+ if not chat:
+ chat = acc.newChat(bud.cfg.uri)
+ chat.showWindow()
+
+ def _onAccContextMenu(self, label):
+ acc = self._getSelectedAccount()
+ if not acc:
+ return
+
+ if label=='Unregister':
+ acc.setRegistration(False)
+ elif label=='Reregister':
+ acc.setRegistration(True)
+ elif label=='Online':
+ ps = pj.PresenceStatus()
+ ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
+ acc.setOnlineStatus(ps)
+ elif label=='Invisible':
+ ps = pj.PresenceStatus()
+ ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE
+ acc.setOnlineStatus(ps)
+ elif label=='Away':
+ ps = pj.PresenceStatus()
+ ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
+ ps.activity = pj.PJRPID_ACTIVITY_AWAY
+ ps.note = "Away"
+ acc.setOnlineStatus(ps)
+ elif label=='Busy':
+ ps = pj.PresenceStatus()
+ ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
+ ps.activity = pj.PJRPID_ACTIVITY_BUSY
+ ps.note = "Busy"
+ acc.setOnlineStatus(ps)
+ elif label=='Settings...':
+ self.cfgChanged = False
+ dlg = accountsetting.Dialog(self.master, acc.cfg)
+ if dlg.doModal():
+ self.updateAccount(acc)
+ acc.modify(acc.cfg)
+ elif label=='Delete...':
+ msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri
+ if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
+ return
+ iid = str(acc.randId)
+ self.accList.remove(acc)
+ acc.setRegistration(False)
+ acc.deleting = True
+ del acc
+ self.tv.delete( (iid,) )
+ elif label=='Add buddy...':
+ cfg = pj.BuddyConfig()
+ dlg = buddy.SettingDialog(self.master, cfg)
+ if dlg.doModal():
+ self._createBuddy(acc, cfg)
+ else:
+ assert not ("Unknown menu " + label)
+
+ def _onBuddyContextMenu(self, label):
+ bud = self._getSelectedBuddy()
+ if not bud:
+ return
+ acc = bud.account
+
+ if label=='Audio call':
+ chat = acc.findChat(bud.cfg.uri)
+ if not chat: chat = acc.newChat(bud.cfg.uri)
+ chat.showWindow()
+ chat.startCall()
+ elif label=='Send instant message':
+ chat = acc.findChat(bud.cfg.uri)
+ if not chat: chat = acc.newChat(bud.cfg.uri)
+ chat.showWindow(True)
+ elif label=='Subscribe':
+ bud.subscribePresence(True)
+ elif label=='Unsubscribe':
+ bud.subscribePresence(False)
+ elif label=='Settings...':
+ subs = bud.cfg.subscribe
+ uri = bud.cfg.uri
+ dlg = buddy.SettingDialog(self.master, bud.cfg)
+ if dlg.doModal():
+ self.updateBuddy(bud)
+ # URI updated?
+ if uri != bud.cfg.uri:
+ cfg = bud.cfg
+ # del old
+ iid = 'buddy' + str(bud.randId)
+ acc.buddyList.remove(bud)
+ del bud
+ self.tv.delete( (iid,) )
+ # add new
+ self._createBuddy(acc, cfg)
+ # presence subscribe setting updated
+ elif subs != bud.cfg.subscribe:
+ bud.subscribePresence(bud.cfg.subscribe)
+ elif label=='Delete...':
+ msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri
+ if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes':
+ return
+ iid = 'buddy' + str(bud.randId)
+ acc.buddyList.remove(bud)
+ del bud
+ self.tv.delete( (iid,) )
+ else:
+ assert not ("Unknown menu " + label)
+
+ def _onTimer(self):
+ if not self.quitting:
+ self.ep.libHandleEvents(10)
+ if not self.quitting:
+ self.master.after(50, self._onTimer)
+
+ def _onClose(self):
+ self.saveConfig()
+ self.quitting = True
+ self.ep.libDestroy()
+ self.ep = None
+ self.update()
+ self.quit()
+
+ def _onMenuAddAccount(self):
+ cfg = pj.AccountConfig()
+ dlg = accountsetting.Dialog(self.master, cfg)
+ if dlg.doModal():
+ self._createAcc(cfg)
+
+ def _onMenuShowHideLogWindow(self):
+ if self.showLogWindow.get():
+ self.logWindow.deiconify()
+ else:
+ self.logWindow.withdraw()
+
+ def _onMenuSettings(self):
+ dlg = settings.Dialog(self, self.appConfig)
+ if dlg.doModal():
+ msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect')
+
+ def _onMenuSaveSettings(self):
+ self.saveConfig()
+
+ def _onMenuQuit(self):
+ self._onClose()
+
+ def _onMenuAbout(self):
+ msgbox.showinfo(self.master.title(), 'About')
+
+
+class ExceptionCatcher:
+ """Custom Tk exception catcher, mainly to display more information
+ from pj.Error exception
+ """
+ def __init__(self, func, subst, widget):
+ self.func = func
+ self.subst = subst
+ self.widget = widget
+ def __call__(self, *args):
+ try:
+ if self.subst:
+ args = apply(self.subst, args)
+ return apply(self.func, args)
+ except pj.Error, error:
+ print 'Exception:'
+ print ' ', error.info()
+ print 'Traceback:'
+ print traceback.print_stack()
+ log.writeLog2(1, 'Exception: ' + error.info() + '\n')
+ except Exception, error:
+ print 'Exception:'
+ print ' ', str(error)
+ print 'Traceback:'
+ print traceback.print_stack()
+ log.writeLog2(1, 'Exception: ' + str(error) + '\n')
+
+def main():
+ #tk.CallWrapper = ExceptionCatcher
+ app = Application()
+ app.start()
+ app.mainloop()
+
+if __name__ == '__main__':
+ main()