Index: /pjproject/trunk/pjsip-apps/src/pygui/account.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/account.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/account.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import random
@@ -37,203 +37,205 @@
 import chat as ch
 
+write=sys.stdout.write
+
 # Account class
 class Account(pj.Account):
-	"""
-	High level Python Account object, derived from pjsua2's Account object.
-	"""
-	def __init__(self, app):
-		pj.Account.__init__(self)
-		self.app = app
-		self.randId = random.randint(1, 9999)
-		self.cfg =  pj.AccountConfig()
-		self.cfgChanged = False
-		self.buddyList = []
-		self.chatList = []
-		self.deleting = False
-
-	def findChat(self, uri_str):
-		uri = ch.ParseSipUri(uri_str)
-		if not uri: return None
-			
-		for chat in self.chatList:
-			if chat.isUriParticipant(uri) and chat.isPrivate():
-				return chat
-		return None
-	
-	def newChat(self, uri_str):
-		uri = ch.ParseSipUri(uri_str)
-		if not uri: return None
-			
-		chat = ch.Chat(self.app, self, uri)
-		self.chatList.append(chat)
-		self.app.updateWindowMenu()
-		return chat
-	
-	def statusText(self):
-		status = '?'
-		if self.isValid():
-			ai = self.getInfo()
-			if ai.regLastErr:
-				status = self.app.ep.utilStrError(ai.regLastErr)
-			elif ai.regIsActive:
-				if ai.onlineStatus:
-					if len(ai.onlineStatusText):
-						status = ai.onlineStatusText
-					else:
-						status = "Online"
-				else:
-					status = "Registered"
-			else:
-				if ai.regIsConfigured:
-					if ai.regStatus/100 == 2:
-						status = "Unregistered"
-					else:
-						status = ai.regStatusText
-				else:
-					status = "Doesn't register"
-		else:
-			status = '- not created -'
-		return status
-        	
-	def onRegState(self, prm):
-		self.app.updateAccount(self)
-
-        def onIncomingCall(self, prm):
-		c = call.Call(self, call_id=prm.callId)
-                call_prm = pj.CallOpParam()
-		call_prm.statusCode = 180
-		c.answer(call_prm)
-		ci = c.getInfo()
-		msg = "Incoming call for account '%s'" % self.cfg.idUri
-		if msgbox.askquestion(msg, "Accept call from '%s'?" % (ci.remoteUri), default=msgbox.YES) == u'yes':
-			call_prm.statusCode = 200
-			c.answer(call_prm)
-			
-			# find/create chat instance
-			chat = self.findChat(ci.remoteUri)
-			if not chat: chat = self.newChat(ci.remoteUri)
-			
-			chat.showWindow()
-			chat.registerCall(ci.remoteUri, c)
-			chat.updateCallState(c, ci)
-		else:
-			c.hangup(call_prm)
-			
-	def onInstantMessage(self, prm):
-		chat = self.findChat(prm.fromUri)
-		if not chat: chat = self.newChat(prm.fromUri)
-		
-		chat.showWindow()
-		chat.addMessage(prm.fromUri, prm.msgBody)
-		
-	def onInstantMessageStatus(self, prm):
-		if prm.code/100 == 2: return
-		
-		chat = self.findChat(prm.toUri)
-		if not chat:
-			print "=== IM status to '%s' cannot find chat" % prm.toUri
-			return
-		
-		chat.addMessage(None, "Failed sending message to '%s': %s" % (prm.toUri, prm.reason))
-		
-	def onTypingIndication(self, prm):
-		chat = self.findChat(prm.fromUri)
-		if not chat:
-			print "=== Incoming typing indication from '%s' cannot find chat" % prm.fromUri
-			return
-		
-		chat.setTypingIndication(prm.fromUri, prm.isTyping)
-
-		
+    """
+    High level Python Account object, derived from pjsua2's Account object.
+    """
+    def __init__(self, app):
+        pj.Account.__init__(self)
+        self.app = app
+        self.randId = random.randint(1, 9999)
+        self.cfg =  pj.AccountConfig()
+        self.cfgChanged = False
+        self.buddyList = []
+        self.chatList = []
+        self.deleting = False
+
+    def findChat(self, uri_str):
+        uri = ch.ParseSipUri(uri_str)
+        if not uri: return None
+
+        for chat in self.chatList:
+            if chat.isUriParticipant(uri) and chat.isPrivate():
+                return chat
+        return None
+
+    def newChat(self, uri_str):
+        uri = ch.ParseSipUri(uri_str)
+        if not uri: return None
+
+        chat = ch.Chat(self.app, self, uri)
+        self.chatList.append(chat)
+        self.app.updateWindowMenu()
+        return chat
+
+    def statusText(self):
+        status = '?'
+        if self.isValid():
+            ai = self.getInfo()
+            if ai.regLastErr:
+                status = self.app.ep.utilStrError(ai.regLastErr)
+            elif ai.regIsActive:
+                if ai.onlineStatus:
+                    if len(ai.onlineStatusText):
+                        status = ai.onlineStatusText
+                    else:
+                        status = "Online"
+                else:
+                    status = "Registered"
+            else:
+                if ai.regIsConfigured:
+                    if ai.regStatus/100 == 2:
+                        status = "Unregistered"
+                    else:
+                        status = ai.regStatusText
+                else:
+                    status = "Doesn't register"
+        else:
+            status = '- not created -'
+        return status
+
+    def onRegState(self, prm):
+        self.app.updateAccount(self)
+
+    def onIncomingCall(self, prm):
+        c = call.Call(self, call_id=prm.callId)
+        call_prm = pj.CallOpParam()
+        call_prm.statusCode = 180
+        c.answer(call_prm)
+        ci = c.getInfo()
+        msg = "Incoming call for account '%s'" % self.cfg.idUri
+        if msgbox.askquestion(msg, "Accept call from '%s'?" % (ci.remoteUri), default=msgbox.YES) == u'yes':
+            call_prm.statusCode = 200
+            c.answer(call_prm)
+
+            # find/create chat instance
+            chat = self.findChat(ci.remoteUri)
+            if not chat: chat = self.newChat(ci.remoteUri)
+
+            chat.showWindow()
+            chat.registerCall(ci.remoteUri, c)
+            chat.updateCallState(c, ci)
+        else:
+            c.hangup(call_prm)
+
+    def onInstantMessage(self, prm):
+        chat = self.findChat(prm.fromUri)
+        if not chat: chat = self.newChat(prm.fromUri)
+
+        chat.showWindow()
+        chat.addMessage(prm.fromUri, prm.msgBody)
+
+    def onInstantMessageStatus(self, prm):
+        if prm.code/100 == 2: return
+
+        chat = self.findChat(prm.toUri)
+        if not chat:
+            write("=== IM status to " + prm.toUri + "cannot find chat\r\n")
+            return
+
+        chat.addMessage(None, "Failed sending message to '%s': %s" % (prm.toUri, prm.reason))
+
+    def onTypingIndication(self, prm):
+        chat = self.findChat(prm.fromUri)
+        if not chat:
+            write("=== Incoming typing indication from " + prm.fromUri + "cannot find chat\r\n")
+            return
+
+        chat.setTypingIndication(prm.fromUri, prm.isTyping)
+
+
 # Account frame, to list accounts
 class AccountListFrame(ttk.Frame):
-	"""
-	This implements a Frame which contains account list and buttons to operate
-	on them (Add, Modify, Delete, etc.). 
-	"""
-	def __init__(self, parent, app, acc_list = []):
-		ttk.Frame.__init__(self, parent, name='acclist')
-		self.app = app
-		self.accList = acc_list
-		self.accDeletedList = []
-		self.pack(expand='yes', fill='both')
-		self._createWidgets()
-		for acc in self.accList:
-			self._showAcc(acc)
-		
-	def _createWidgets(self):
-		self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse')
-		self.tv.heading('#0', text='Priority')
-		self.tv.heading(0, text='ID')
-		self.tv.heading(1, text='Registrar')
-		self.tv.heading(2, text='Default?')
-		self.tv.column('#0', width=60)
-		self.tv.column(0, width=300)
-		self.tv.column(1, width=200)
-		self.tv.column(2, width=60)
-		self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5)
-		
-		ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5)
-		ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1)
-		ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2)
-		ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3)
-
-	def _showAcc(self, acc):
-		is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
-		values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
-		self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values)
-	
-	def updateAccount(self, acc):
-		is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
-		values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
-		self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values)
-	
-	def _getSelectedAcc(self):
-		items = self.tv.selection()
-		if not items:
-			return None
-		iid = int(items[0])
-		return [acc for acc in self.accList if acc.randId==iid][0]
-	
-	def _onBtnAdd(self):
-		cfg = pj.AccountConfig()
-		dlg = accountsetting.Dialog(self.master, cfg)
-		if dlg.doModal():
-			acc = Account(self.app)
-			acc.cfg = cfg
-			self._showAcc(acc)
-			self.accList.append(acc)
-			self.cfgChanged = True
-	
-	def _onBtnSettings(self):
-		acc = self._getSelectedAcc()
-		if not acc:
-			return
-		dlg = accountsetting.Dialog(self.master, acc.cfg)
-		if dlg.doModal():
-			self.updateAccount(acc)
-			self.cfgChanged = True
-
-	def _onBtnDelete(self):
-		acc = self._getSelectedAcc()
-		if not acc:
-			return
-		msg = "Do you really want to delete account '%s'" % acc.cfg.idUri
-		if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
-			return
-		self.accList.remove(acc)
-		self.accDeletedList.append(acc)
-		self.tv.delete( (str(acc.randId),) )
-
-	def _onBtnSetDefault(self):
-		acc = self._getSelectedAcc()
-		if not acc:
-			return
-		if acc.isValid():
-			acc.setDefault()
-		for acc in self.accList:
-			self.updateAccount(acc)
-		
-		
+    """
+    This implements a Frame which contains account list and buttons to operate
+    on them (Add, Modify, Delete, etc.).
+    """
+    def __init__(self, parent, app, acc_list = []):
+        ttk.Frame.__init__(self, parent, name='acclist')
+        self.app = app
+        self.accList = acc_list
+        self.accDeletedList = []
+        self.pack(expand='yes', fill='both')
+        self._createWidgets()
+        for acc in self.accList:
+            self._showAcc(acc)
+
+    def _createWidgets(self):
+        self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse')
+        self.tv.heading('#0', text='Priority')
+        self.tv.heading(0, text='ID')
+        self.tv.heading(1, text='Registrar')
+        self.tv.heading(2, text='Default?')
+        self.tv.column('#0', width=60)
+        self.tv.column(0, width=300)
+        self.tv.column(1, width=200)
+        self.tv.column(2, width=60)
+        self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5)
+
+        ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5)
+        ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1)
+        ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2)
+        ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3)
+
+    def _showAcc(self, acc):
+        is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
+        values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
+        self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values)
+
+    def updateAccount(self, acc):
+        is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
+        values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
+        self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values)
+
+    def _getSelectedAcc(self):
+        items = self.tv.selection()
+        if not items:
+            return None
+        iid = int(items[0])
+        return [acc for acc in self.accList if acc.randId==iid][0]
+
+    def _onBtnAdd(self):
+        cfg = pj.AccountConfig()
+        dlg = accountsetting.Dialog(self.master, cfg)
+        if dlg.doModal():
+            acc = Account(self.app)
+            acc.cfg = cfg
+            self._showAcc(acc)
+            self.accList.append(acc)
+            self.cfgChanged = True
+
+    def _onBtnSettings(self):
+        acc = self._getSelectedAcc()
+        if not acc:
+            return
+        dlg = accountsetting.Dialog(self.master, acc.cfg)
+        if dlg.doModal():
+            self.updateAccount(acc)
+            self.cfgChanged = True
+
+    def _onBtnDelete(self):
+        acc = self._getSelectedAcc()
+        if not acc:
+            return
+        msg = "Do you really want to delete account '%s'" % acc.cfg.idUri
+        if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
+            return
+        self.accList.remove(acc)
+        self.accDeletedList.append(acc)
+        self.tv.delete( (str(acc.randId),) )
+
+    def _onBtnSetDefault(self):
+        acc = self._getSelectedAcc()
+        if not acc:
+            return
+        if acc.isValid():
+            acc.setDefault()
+        for acc in self.accList:
+            self.updateAccount(acc)
+
+
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/accountsetting.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/accountsetting.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/accountsetting.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import pjsua2 as pj
@@ -34,336 +34,336 @@
 
 class Dialog(tk.Toplevel):
-	"""
-	This implements account settings dialog to manipulate account settings.
-	"""
-	def __init__(self, parent, cfg):
-		tk.Toplevel.__init__(self, parent)
-		self.transient(parent)
-		self.parent = parent
-		self.geometry("+100+100")
-		self.title('Account settings')
-		
-		self.frm = ttk.Frame(self)
-		self.frm.pack(expand='yes', fill='both')
-		
-		self.isOk = False
-		self.cfg = cfg
-		
-		self.createWidgets()
-	
-	def doModal(self):
-		if self.parent:
-			self.parent.wait_window(self)
-		else:
-			self.wait_window(self)
-		return self.isOk
-		
-	def createWidgets(self):
-		# The notebook
-		self.frm.rowconfigure(0, weight=1)
-		self.frm.rowconfigure(1, weight=0)
-		self.frm.columnconfigure(0, weight=1)
-		self.frm.columnconfigure(1, weight=1)
-		self.wTab = ttk.Notebook(self.frm)
-		self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E)
-		
-		# Main buttons
-		btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
-		btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
-		btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
-		btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
-		
-		# Tabs
-		self.createBasicTab()
-		self.createSipTab()
-		self.createMediaTab()
-		self.createMediaNatTab()
-		
-	def createBasicTab(self):
-		# Prepare the variables to set/receive values from GUI
-		self.cfgPriority = tk.IntVar(value=self.cfg.priority)
-		self.cfgAccId = tk.StringVar(value=self.cfg.idUri)
-		self.cfgRegistrar = tk.StringVar(value=self.cfg.regConfig.registrarUri)
-		self.cfgRegisterOnAdd = tk.IntVar(value=self.cfg.regConfig.registerOnAdd)
-		self.cfgUsername = tk.StringVar()
-		self.cfgPassword = tk.StringVar()
-		if len(self.cfg.sipConfig.authCreds):
-			self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username )
-			self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data )
-		self.cfgProxy = tk.StringVar()
-		if len(self.cfg.sipConfig.proxies):
-			self.cfgProxy.set( self.cfg.sipConfig.proxies[0] )
-		
-		# Build the tab page
-		frm = ttk.Frame(self.frm)
-		frm.columnconfigure(0, weight=1)
-		frm.columnconfigure(1, weight=2)
-		row = 0
-		ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgAccId, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgRegistrar, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgProxy, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Auth username:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgUsername, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-
-		self.wTab.add(frm, text='Basic Settings')
-		
-
-	def createSipTab(self):
-		# Prepare the variables to set/receive values from GUI
-		self.cfgPrackUse 	= tk.IntVar(value=self.cfg.callConfig.prackUse)
-		self.cfgTimerUse 	= tk.IntVar(value=self.cfg.callConfig.timerUse)
-		self.cfgTimerExpires 	= tk.IntVar(value=self.cfg.callConfig.timerSessExpiresSec)
-		self.cfgPublish 	= tk.BooleanVar(value=self.cfg.presConfig.publishEnabled)
-		self.cfgMwiEnabled 	= tk.BooleanVar(value=self.cfg.mwiConfig.enabled)
-		self.cfgEnableContactRewrite = tk.BooleanVar(value=self.cfg.natConfig.contactRewriteUse != 0) 
-		self.cfgEnableViaRewrite = tk.BooleanVar(value=self.cfg.natConfig.viaRewriteUse != 0) 
-		self.cfgEnableSdpRewrite = tk.BooleanVar(value=self.cfg.natConfig.sdpNatRewriteUse != 0)
-		self.cfgEnableSipOutbound = tk.BooleanVar(value=self.cfg.natConfig.sipOutboundUse != 0)
-		self.cfgKaInterval 	= tk.IntVar(value=self.cfg.natConfig.udpKaIntervalSec)
-		
-		# Build the tab page
-		frm = ttk.Frame(self.frm)
-		frm.columnconfigure(0, weight=1)
-		frm.columnconfigure(1, weight=2)
-		row = 0
-		ttk.Label(frm, text='100rel/PRACK:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Only offer PRACK', value=pj.PJSUA_100REL_NOT_USED, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Offer and use if remote supports', value=pj.PJSUA_100REL_OPTIONAL, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_100REL_MANDATORY, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Session Timer:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Not offered', value=pj.PJSUA_SIP_TIMER_INACTIVE, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Optional', value=pj.PJSUA_SIP_TIMER_OPTIONAL, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_SIP_TIMER_REQUIRED, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text="Always use", value=pj.PJSUA_SIP_TIMER_ALWAYS, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Session Timer Expiration:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		tk.Spinbox(frm, from_=90, to=7200, textvariable=self.cfgTimerExpires, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
-		ttk.Label(frm, text='(seconds)').grid(row=row, column=1, sticky=tk.E)
-		row += 1
-		ttk.Label(frm, text='Presence:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable PUBLISH', variable=self.cfgPublish).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='Message Waiting Indication:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable MWI', variable=self.cfgMwiEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='NAT Traversal:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable Contact Rewrite', variable=self.cfgEnableContactRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Enable Via Rewrite', variable=self.cfgEnableViaRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Enable SDP IP Address Rewrite', variable=self.cfgEnableSdpRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Enable SIP Outbound Extension', variable=self.cfgEnableSipOutbound).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='UDP Keep-Alive Interval:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		tk.Spinbox(frm, from_=0, to=3600, textvariable=self.cfgKaInterval, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
-		ttk.Label(frm, text='(seconds) Zero to disable.').grid(row=row, column=1, sticky=tk.E)
-
-
-		self.wTab.add(frm, text='SIP Features')
-
-	def createMediaTab(self):
-		# Prepare the variables to set/receive values from GUI
-		self.cfgMedPort = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.port)
-		self.cfgMedPortRange = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.portRange)
-		self.cfgMedLockCodec = tk.BooleanVar(value=self.cfg.mediaConfig.lockCodecEnabled)
-		self.cfgMedSrtp = tk.IntVar(value=self.cfg.mediaConfig.srtpUse)
-		self.cfgMedSrtpSecure = tk.IntVar(value=self.cfg.mediaConfig.srtpSecureSignaling)
-		self.cfgMedIpv6 = tk.BooleanVar(value=self.cfg.mediaConfig.ipv6Use==pj.PJSUA_IPV6_ENABLED)
-		
-		# Build the tab page
-		frm = ttk.Frame(self.frm)
-		frm.columnconfigure(0, weight=1)
-		frm.columnconfigure(1, weight=21)
-		row = 0
-		ttk.Label(frm, text='Secure RTP (SRTP):').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Disable', value=pj.PJMEDIA_SRTP_DISABLED, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Mandatory', value=pj.PJMEDIA_SRTP_MANDATORY, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Optional (non-standard)', value=pj.PJMEDIA_SRTP_OPTIONAL, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='SRTP signaling:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Does not require secure signaling', value=0, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Require secure next hop (TLS)', value=1, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Require secure end-to-end (SIPS)', value=2, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='RTP transport start port:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
-		ttk.Label(frm, text='(0: any)').grid(row=row, column=1, sticky=tk.E, pady=2)
-		row += 1
-		ttk.Label(frm, text='Port range:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPortRange, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
-		ttk.Label(frm, text='(0: not limited)').grid(row=row, column=1, sticky=tk.E, pady=2)
-		row += 1
-		ttk.Label(frm, text='Lock codec:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable', variable=self.cfgMedLockCodec).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='Use IPv6:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Yes', variable=self.cfgMedIpv6).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-
-		self.wTab.add(frm, text='Media settings')
-
-	def createMediaNatTab(self):
-		# Prepare the variables to set/receive values from GUI
-		self.cfgSipUseStun = tk.IntVar(value = self.cfg.natConfig.sipStunUse)
-		self.cfgMediaUseStun = tk.IntVar(value = self.cfg.natConfig.mediaStunUse)
-		self.cfgIceEnabled = tk.BooleanVar(value = self.cfg.natConfig.iceEnabled)
-		self.cfgIceAggressive = tk.BooleanVar(value = self.cfg.natConfig.iceAggressiveNomination)
-		self.cfgAlwaysUpdate = tk.BooleanVar(value = True if self.cfg.natConfig.iceAlwaysUpdate else False)
-		self.cfgIceNoHostCands = tk.BooleanVar(value = True if self.cfg.natConfig.iceMaxHostCands == 0 else False)
-		self.cfgTurnEnabled = tk.BooleanVar(value = self.cfg.natConfig.turnEnabled)
-		self.cfgTurnServer = tk.StringVar(value = self.cfg.natConfig.turnServer)
-		self.cfgTurnConnType = tk.IntVar(value = self.cfg.natConfig.turnConnType)
-		self.cfgTurnUser = tk.StringVar(value = self.cfg.natConfig.turnUserName)
-		self.cfgTurnPasswd = tk.StringVar(value = self.cfg.natConfig.turnPassword)
-		
-		# Build the tab page
-		frm = ttk.Frame(self.frm)
-		frm.columnconfigure(0, weight=1)
-		frm.columnconfigure(1, weight=2)
-		row = 0
-		ttk.Label(frm, text='SIP STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='Media STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='ICE:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable', variable=self.cfgIceEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Use aggresive nomination', variable=self.cfgIceAggressive).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Always re-INVITE after negotiation', variable=self.cfgAlwaysUpdate).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Checkbutton(frm, text='Disable host candidates', variable=self.cfgIceNoHostCands).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='TURN:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Checkbutton(frm, text='Enable', variable=self.cfgTurnEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
-		row += 1
-		ttk.Label(frm, text='TURN server:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgTurnServer, width=20).grid(row=row, column=1, sticky=tk.W, padx=6)
-		ttk.Label(frm, text='host[:port]').grid(row=row, column=1, sticky=tk.E, pady=6)
-		row += 1
-		ttk.Label(frm, text='TURN connection:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Radiobutton(frm, text='UDP', value=pj.PJ_TURN_TP_UDP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Radiobutton(frm, text='TCP', value=pj.PJ_TURN_TP_TCP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='TURN username:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgTurnUser, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
-		row += 1
-		ttk.Label(frm, text='TURN password:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgTurnPasswd, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
-
-		self.wTab.add(frm, text='NAT settings')
-		
-	def onOk(self):
-		# Check basic settings
-		errors = "";
-		if not self.cfgAccId.get():
-			errors += "Account ID is required\n"
-		if self.cfgAccId.get():
-			if not endpoint.validateSipUri(self.cfgAccId.get()):
-				errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get())
-		if self.cfgRegistrar.get():
-			if not endpoint.validateSipUri(self.cfgRegistrar.get()):
-				errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get())
-		if self.cfgProxy.get():
-			if not endpoint.validateSipUri(self.cfgProxy.get()):
-				errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get())
-		if self.cfgTurnEnabled.get():
-			if not self.cfgTurnServer.get():
-				errors += "TURN server is required\n"
-		if errors:
-			msgbox.showerror("Error detected:", errors)
-			return
-		
-		# Basic settings
-		self.cfg.priority = self.cfgPriority.get()
-		self.cfg.idUri = self.cfgAccId.get()
-		self.cfg.regConfig.registrarUri = self.cfgRegistrar.get()
-		self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get()
-		while len(self.cfg.sipConfig.authCreds):
-			self.cfg.sipConfig.authCreds.pop()
-		if self.cfgUsername.get():
-			cred = pj.AuthCredInfo()
-			cred.scheme = "digest"
-			cred.realm = "*"
-			cred.username = self.cfgUsername.get()
-			cred.data = self.cfgPassword.get()
-			self.cfg.sipConfig.authCreds.append(cred)
-		while len(self.cfg.sipConfig.proxies):
-			self.cfg.sipConfig.proxies.pop()
-		if self.cfgProxy.get():
-			self.cfg.sipConfig.proxies.append(self.cfgProxy.get())
-
-		# SIP features
-		self.cfg.callConfig.prackUse		= self.cfgPrackUse.get() 
-		self.cfg.callConfig.timerUse		= self.cfgTimerUse.get()
-		self.cfg.callConfig.timerSessExpiresSec	= self.cfgTimerExpires.get() 
-		self.cfg.presConfig.publishEnabled	= self.cfgPublish.get() 
-		self.cfg.mwiConfig.enabled		= self.cfgMwiEnabled.get() 
-		self.cfg.natConfig.contactRewriteUse	= 1 if self.cfgEnableContactRewrite.get() else 0
-		self.cfg.natConfig.viaRewriteUse 	= 1 if self.cfgEnableViaRewrite.get() else 0
-		self.cfg.natConfig.sdpNatRewriteUse	= 1 if self.cfgEnableSdpRewrite.get() else 0
-		self.cfg.natConfig.sipOutboundUse	= 1 if self.cfgEnableSipOutbound.get() else 0
-		self.cfg.natConfig.udpKaIntervalSec	= self.cfgKaInterval.get()
-
-		# Media
-		self.cfg.mediaConfig.transportConfig.port	= self.cfgMedPort.get()
-		self.cfg.mediaConfig.transportConfig.portRange	= self.cfgMedPortRange.get()
-		self.cfg.mediaConfig.lockCodecEnabled		= self.cfgMedLockCodec.get()
-		self.cfg.mediaConfig.srtpUse			= self.cfgMedSrtp.get()
-		self.cfg.mediaConfig.srtpSecureSignaling	= self.cfgMedSrtpSecure.get()
-		self.cfg.mediaConfig.ipv6Use			= pj.PJSUA_IPV6_ENABLED if self.cfgMedIpv6.get() else pj.PJSUA_IPV6_DISABLED
-		
-		# NAT
-		self.cfg.natConfig.sipStunUse		= self.cfgSipUseStun.get()
-		self.cfg.natConfig.mediaStunUse		= self.cfgMediaUseStun.get()
-		self.cfg.natConfig.iceEnabled		= self.cfgIceEnabled.get()
-		self.cfg.natConfig.iceAggressiveNomination = self.cfgIceAggressive .get()
-		self.cfg.natConfig.iceAlwaysUpdate	= self.cfgAlwaysUpdate.get()
-		self.cfg.natConfig.iceMaxHostCands	= 0 if self.cfgIceNoHostCands.get() else -1 
-		self.cfg.natConfig.turnEnabled		= self.cfgTurnEnabled.get()
-		self.cfg.natConfig.turnServer		= self.cfgTurnServer.get()
-		self.cfg.natConfig.turnConnType		= self.cfgTurnConnType.get()
-		self.cfg.natConfig.turnUserName		= self.cfgTurnUser.get()
-		self.cfg.natConfig.turnPasswordType	= 0
-		self.cfg.natConfig.turnPassword		= self.cfgTurnPasswd.get()
-		
-		self.isOk = True
-		self.destroy()
-		
-	def onCancel(self):
-		self.destroy()
+    """
+    This implements account settings dialog to manipulate account settings.
+    """
+    def __init__(self, parent, cfg):
+        tk.Toplevel.__init__(self, parent)
+        self.transient(parent)
+        self.parent = parent
+        self.geometry("+100+100")
+        self.title('Account settings')
+
+        self.frm = ttk.Frame(self)
+        self.frm.pack(expand='yes', fill='both')
+
+        self.isOk = False
+        self.cfg = cfg
+
+        self.createWidgets()
+
+    def doModal(self):
+        if self.parent:
+            self.parent.wait_window(self)
+        else:
+            self.wait_window(self)
+        return self.isOk
+
+    def createWidgets(self):
+        # The notebook
+        self.frm.rowconfigure(0, weight=1)
+        self.frm.rowconfigure(1, weight=0)
+        self.frm.columnconfigure(0, weight=1)
+        self.frm.columnconfigure(1, weight=1)
+        self.wTab = ttk.Notebook(self.frm)
+        self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E)
+
+        # Main buttons
+        btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
+        btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
+        btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
+        btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
+
+        # Tabs
+        self.createBasicTab()
+        self.createSipTab()
+        self.createMediaTab()
+        self.createMediaNatTab()
+
+    def createBasicTab(self):
+        # Prepare the variables to set/receive values from GUI
+        self.cfgPriority = tk.IntVar(value=self.cfg.priority)
+        self.cfgAccId = tk.StringVar(value=self.cfg.idUri)
+        self.cfgRegistrar = tk.StringVar(value=self.cfg.regConfig.registrarUri)
+        self.cfgRegisterOnAdd = tk.BooleanVar(value=self.cfg.regConfig.registerOnAdd)
+        self.cfgUsername = tk.StringVar()
+        self.cfgPassword = tk.StringVar()
+        if len(self.cfg.sipConfig.authCreds):
+            self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username )
+            self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data )
+        self.cfgProxy = tk.StringVar()
+        if len(self.cfg.sipConfig.proxies):
+            self.cfgProxy.set( self.cfg.sipConfig.proxies[0] )
+
+        # Build the tab page
+        frm = ttk.Frame(self.frm)
+        frm.columnconfigure(0, weight=1)
+        frm.columnconfigure(1, weight=2)
+        row = 0
+        ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgAccId, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgRegistrar, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgProxy, width=32).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Auth username:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgUsername, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+
+        self.wTab.add(frm, text='Basic Settings')
+
+
+    def createSipTab(self):
+        # Prepare the variables to set/receive values from GUI
+        self.cfgPrackUse 	= tk.IntVar(value=self.cfg.callConfig.prackUse)
+        self.cfgTimerUse 	= tk.IntVar(value=self.cfg.callConfig.timerUse)
+        self.cfgTimerExpires 	= tk.IntVar(value=self.cfg.callConfig.timerSessExpiresSec)
+        self.cfgPublish 	= tk.BooleanVar(value=self.cfg.presConfig.publishEnabled)
+        self.cfgMwiEnabled 	= tk.BooleanVar(value=self.cfg.mwiConfig.enabled)
+        self.cfgEnableContactRewrite = tk.BooleanVar(value=self.cfg.natConfig.contactRewriteUse != 0)
+        self.cfgEnableViaRewrite = tk.BooleanVar(value=self.cfg.natConfig.viaRewriteUse != 0)
+        self.cfgEnableSdpRewrite = tk.BooleanVar(value=self.cfg.natConfig.sdpNatRewriteUse != 0)
+        self.cfgEnableSipOutbound = tk.BooleanVar(value=self.cfg.natConfig.sipOutboundUse != 0)
+        self.cfgKaInterval 	= tk.IntVar(value=self.cfg.natConfig.udpKaIntervalSec)
+
+        # Build the tab page
+        frm = ttk.Frame(self.frm)
+        frm.columnconfigure(0, weight=1)
+        frm.columnconfigure(1, weight=2)
+        row = 0
+        ttk.Label(frm, text='100rel/PRACK:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Only offer PRACK', value=pj.PJSUA_100REL_NOT_USED, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Offer and use if remote supports', value=pj.PJSUA_100REL_OPTIONAL, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_100REL_MANDATORY, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Session Timer:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Not offered', value=pj.PJSUA_SIP_TIMER_INACTIVE, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Optional', value=pj.PJSUA_SIP_TIMER_OPTIONAL, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_SIP_TIMER_REQUIRED, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text="Always use", value=pj.PJSUA_SIP_TIMER_ALWAYS, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Session Timer Expiration:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        tk.Spinbox(frm, from_=90, to=7200, textvariable=self.cfgTimerExpires, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
+        ttk.Label(frm, text='(seconds)').grid(row=row, column=1, sticky=tk.E)
+        row += 1
+        ttk.Label(frm, text='Presence:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable PUBLISH', variable=self.cfgPublish).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='Message Waiting Indication:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable MWI', variable=self.cfgMwiEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='NAT Traversal:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable Contact Rewrite', variable=self.cfgEnableContactRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Enable Via Rewrite', variable=self.cfgEnableViaRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Enable SDP IP Address Rewrite', variable=self.cfgEnableSdpRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Enable SIP Outbound Extension', variable=self.cfgEnableSipOutbound).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='UDP Keep-Alive Interval:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        tk.Spinbox(frm, from_=0, to=3600, textvariable=self.cfgKaInterval, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
+        ttk.Label(frm, text='(seconds) Zero to disable.').grid(row=row, column=1, sticky=tk.E)
+
+
+        self.wTab.add(frm, text='SIP Features')
+
+    def createMediaTab(self):
+        # Prepare the variables to set/receive values from GUI
+        self.cfgMedPort = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.port)
+        self.cfgMedPortRange = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.portRange)
+        self.cfgMedLockCodec = tk.BooleanVar(value=self.cfg.mediaConfig.lockCodecEnabled)
+        self.cfgMedSrtp = tk.IntVar(value=self.cfg.mediaConfig.srtpUse)
+        self.cfgMedSrtpSecure = tk.IntVar(value=self.cfg.mediaConfig.srtpSecureSignaling)
+        self.cfgMedIpv6 = tk.BooleanVar(value=self.cfg.mediaConfig.ipv6Use==pj.PJSUA_IPV6_ENABLED)
+
+        # Build the tab page
+        frm = ttk.Frame(self.frm)
+        frm.columnconfigure(0, weight=1)
+        frm.columnconfigure(1, weight=21)
+        row = 0
+        ttk.Label(frm, text='Secure RTP (SRTP):').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Disable', value=pj.PJMEDIA_SRTP_DISABLED, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Mandatory', value=pj.PJMEDIA_SRTP_MANDATORY, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Optional (non-standard)', value=pj.PJMEDIA_SRTP_OPTIONAL, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='SRTP signaling:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Does not require secure signaling', value=0, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Require secure next hop (TLS)', value=1, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Require secure end-to-end (SIPS)', value=2, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='RTP transport start port:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
+        ttk.Label(frm, text='(0: any)').grid(row=row, column=1, sticky=tk.E, pady=2)
+        row += 1
+        ttk.Label(frm, text='Port range:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPortRange, width=5).grid(row=row, column=1, sticky=tk.W, padx=6)
+        ttk.Label(frm, text='(0: not limited)').grid(row=row, column=1, sticky=tk.E, pady=2)
+        row += 1
+        ttk.Label(frm, text='Lock codec:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable', variable=self.cfgMedLockCodec).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='Use IPv6:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Yes', variable=self.cfgMedIpv6).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+
+        self.wTab.add(frm, text='Media settings')
+
+    def createMediaNatTab(self):
+        # Prepare the variables to set/receive values from GUI
+        self.cfgSipUseStun = tk.IntVar(value = self.cfg.natConfig.sipStunUse)
+        self.cfgMediaUseStun = tk.IntVar(value = self.cfg.natConfig.mediaStunUse)
+        self.cfgIceEnabled = tk.BooleanVar(value = self.cfg.natConfig.iceEnabled)
+        self.cfgIceAggressive = tk.BooleanVar(value = self.cfg.natConfig.iceAggressiveNomination)
+        self.cfgAlwaysUpdate = tk.BooleanVar(value = True if self.cfg.natConfig.iceAlwaysUpdate else False)
+        self.cfgIceNoHostCands = tk.BooleanVar(value = True if self.cfg.natConfig.iceMaxHostCands == 0 else False)
+        self.cfgTurnEnabled = tk.BooleanVar(value = self.cfg.natConfig.turnEnabled)
+        self.cfgTurnServer = tk.StringVar(value = self.cfg.natConfig.turnServer)
+        self.cfgTurnConnType = tk.IntVar(value = self.cfg.natConfig.turnConnType)
+        self.cfgTurnUser = tk.StringVar(value = self.cfg.natConfig.turnUserName)
+        self.cfgTurnPasswd = tk.StringVar(value = self.cfg.natConfig.turnPassword)
+
+        # Build the tab page
+        frm = ttk.Frame(self.frm)
+        frm.columnconfigure(0, weight=1)
+        frm.columnconfigure(1, weight=2)
+        row = 0
+        ttk.Label(frm, text='SIP STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='Media STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='ICE:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable', variable=self.cfgIceEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Use aggresive nomination', variable=self.cfgIceAggressive).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Always re-INVITE after negotiation', variable=self.cfgAlwaysUpdate).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Checkbutton(frm, text='Disable host candidates', variable=self.cfgIceNoHostCands).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='TURN:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Checkbutton(frm, text='Enable', variable=self.cfgTurnEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+        row += 1
+        ttk.Label(frm, text='TURN server:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgTurnServer, width=20).grid(row=row, column=1, sticky=tk.W, padx=6)
+        ttk.Label(frm, text='host[:port]').grid(row=row, column=1, sticky=tk.E, pady=6)
+        row += 1
+        ttk.Label(frm, text='TURN connection:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Radiobutton(frm, text='UDP', value=pj.PJ_TURN_TP_UDP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Radiobutton(frm, text='TCP', value=pj.PJ_TURN_TP_TCP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='TURN username:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgTurnUser, width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
+        row += 1
+        ttk.Label(frm, text='TURN password:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgTurnPasswd, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6)
+
+        self.wTab.add(frm, text='NAT settings')
+
+    def onOk(self):
+        # Check basic settings
+        errors = "";
+        if not self.cfgAccId.get():
+            errors += "Account ID is required\n"
+        if self.cfgAccId.get():
+            if not endpoint.validateSipUri(self.cfgAccId.get()):
+                errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get())
+        if self.cfgRegistrar.get():
+            if not endpoint.validateSipUri(self.cfgRegistrar.get()):
+                errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get())
+        if self.cfgProxy.get():
+            if not endpoint.validateSipUri(self.cfgProxy.get()):
+                errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get())
+        if self.cfgTurnEnabled.get():
+            if not self.cfgTurnServer.get():
+                errors += "TURN server is required\n"
+        if errors:
+            msgbox.showerror("Error detected:", errors)
+            return
+
+        # Basic settings
+        self.cfg.priority = self.cfgPriority.get()
+        self.cfg.idUri = self.cfgAccId.get()
+        self.cfg.regConfig.registrarUri = self.cfgRegistrar.get()
+        self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get()
+        while len(self.cfg.sipConfig.authCreds):
+            self.cfg.sipConfig.authCreds.pop()
+        if self.cfgUsername.get():
+            cred = pj.AuthCredInfo()
+            cred.scheme = "digest"
+            cred.realm = "*"
+            cred.username = self.cfgUsername.get()
+            cred.data = self.cfgPassword.get()
+            self.cfg.sipConfig.authCreds.append(cred)
+        while len(self.cfg.sipConfig.proxies):
+            self.cfg.sipConfig.proxies.pop()
+        if self.cfgProxy.get():
+            self.cfg.sipConfig.proxies.append(self.cfgProxy.get())
+
+        # SIP features
+        self.cfg.callConfig.prackUse		= self.cfgPrackUse.get()
+        self.cfg.callConfig.timerUse		= self.cfgTimerUse.get()
+        self.cfg.callConfig.timerSessExpiresSec	= self.cfgTimerExpires.get()
+        self.cfg.presConfig.publishEnabled	= self.cfgPublish.get()
+        self.cfg.mwiConfig.enabled		= self.cfgMwiEnabled.get()
+        self.cfg.natConfig.contactRewriteUse	= 1 if self.cfgEnableContactRewrite.get() else 0
+        self.cfg.natConfig.viaRewriteUse 	= 1 if self.cfgEnableViaRewrite.get() else 0
+        self.cfg.natConfig.sdpNatRewriteUse	= 1 if self.cfgEnableSdpRewrite.get() else 0
+        self.cfg.natConfig.sipOutboundUse	= 1 if self.cfgEnableSipOutbound.get() else 0
+        self.cfg.natConfig.udpKaIntervalSec	= self.cfgKaInterval.get()
+
+        # Media
+        self.cfg.mediaConfig.transportConfig.port	= self.cfgMedPort.get()
+        self.cfg.mediaConfig.transportConfig.portRange	= self.cfgMedPortRange.get()
+        self.cfg.mediaConfig.lockCodecEnabled		= self.cfgMedLockCodec.get()
+        self.cfg.mediaConfig.srtpUse			= self.cfgMedSrtp.get()
+        self.cfg.mediaConfig.srtpSecureSignaling	= self.cfgMedSrtpSecure.get()
+        self.cfg.mediaConfig.ipv6Use			= pj.PJSUA_IPV6_ENABLED if self.cfgMedIpv6.get() else pj.PJSUA_IPV6_DISABLED
+
+        # NAT
+        self.cfg.natConfig.sipStunUse		= self.cfgSipUseStun.get()
+        self.cfg.natConfig.mediaStunUse		= self.cfgMediaUseStun.get()
+        self.cfg.natConfig.iceEnabled		= self.cfgIceEnabled.get()
+        self.cfg.natConfig.iceAggressiveNomination = self.cfgIceAggressive .get()
+        self.cfg.natConfig.iceAlwaysUpdate	= self.cfgAlwaysUpdate.get()
+        self.cfg.natConfig.iceMaxHostCands	= 0 if self.cfgIceNoHostCands.get() else -1
+        self.cfg.natConfig.turnEnabled		= self.cfgTurnEnabled.get()
+        self.cfg.natConfig.turnServer		= self.cfgTurnServer.get()
+        self.cfg.natConfig.turnConnType		= self.cfgTurnConnType.get()
+        self.cfg.natConfig.turnUserName		= self.cfgTurnUser.get()
+        self.cfg.natConfig.turnPasswordType	= 0
+        self.cfg.natConfig.turnPassword		= self.cfgTurnPasswd.get()
+
+        self.isOk = True
+        self.destroy()
+
+    def onCancel(self):
+        self.destroy()
 
 
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/application.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/application.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/application.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import pjsua2 as pj
@@ -45,484 +45,486 @@
 # http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2014-March/017223.html
 USE_THREADS = False
-	
+
+write=sys.stdout.write
+
 class Application(ttk.Frame):
-	"""
-	The Application main frame.
-	"""
-	def __init__(self):
-		global USE_THREADS
-		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()
-		if USE_THREADS:
-			self.appConfig.epConfig.uaConfig.threadCnt = 1
-			self.appConfig.epConfig.uaConfig.mainThreadOnly = False
-		else:
-			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'):
-		global USE_THREADS
-		# Load config
-		if cfg_file and os.path.exists(cfg_file):
-			self.appConfig.loadFile(cfg_file)
-
-		if USE_THREADS:
-			self.appConfig.epConfig.uaConfig.threadCnt = 1
-			self.appConfig.epConfig.uaConfig.mainThreadOnly = False
-		else:
-			self.appConfig.epConfig.uaConfig.threadCnt = 0
-			self.appConfig.epConfig.uaConfig.mainThreadOnly = True
-		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
-		if not USE_THREADS:
-			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')
-		
+    """
+    The Application main frame.
+    """
+    def __init__(self):
+        global USE_THREADS
+        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()
+        if USE_THREADS:
+            self.appConfig.epConfig.uaConfig.threadCnt = 1
+            self.appConfig.epConfig.uaConfig.mainThreadOnly = False
+        else:
+            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'):
+        global USE_THREADS
+        # Load config
+        if cfg_file and os.path.exists(cfg_file):
+            self.appConfig.loadFile(cfg_file)
+
+        if USE_THREADS:
+            self.appConfig.epConfig.uaConfig.threadCnt = 1
+            self.appConfig.epConfig.uaConfig.mainThreadOnly = False
+        else:
+            self.appConfig.epConfig.uaConfig.threadCnt = 0
+            self.appConfig.epConfig.uaConfig.mainThreadOnly = True
+        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
+        if not USE_THREADS:
+            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')
+    """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 as error:
+            write("Exception:\r\n")
+            write("  ," + error.info() + "\r\n")
+            write("Traceback:\r\n")
+            write(traceback.print_stack())
+            log.writeLog2(1, 'Exception: ' + error.info() + '\n')
+        except Exception as error:
+            write("Exception:\r\n")
+            write("  ," +  str(error) + "\r\n")
+            write("Traceback:\r\n")
+            write(traceback.print_stack())
+            log.writeLog2(1, 'Exception: ' + str(error) + '\n')
 
 def main():
-	#tk.CallWrapper = ExceptionCatcher
-	app = Application()
-	app.start()
-	app.mainloop()
-		
+    #tk.CallWrapper = ExceptionCatcher
+    app = Application()
+    app.start()
+    app.mainloop()
+
 if __name__ == '__main__':
-	main()
+    main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/buddy.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/buddy.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/buddy.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import random
@@ -36,117 +36,117 @@
 # Buddy class
 class Buddy(pj.Buddy):
-	"""
-	High level Python Buddy object, derived from pjsua2's Buddy object.
-	"""
-	def __init__(self, app):
-		pj.Buddy.__init__(self)
-		self.app = app
-		self.randId = random.randint(1, 9999)
-		self.cfg = None
-		self.account = None
+    """
+    High level Python Buddy object, derived from pjsua2's Buddy object.
+    """
+    def __init__(self, app):
+        pj.Buddy.__init__(self)
+        self.app = app
+        self.randId = random.randint(1, 9999)
+        self.cfg = None
+        self.account = None
 
-	def statusText(self):
-		bi = self.getInfo()
-		status = ''
-		if bi.subState == pj.PJSIP_EVSUB_STATE_ACTIVE:
-			if bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE:
-				status = bi.presStatus.statusText
-				if not status:
-					status = 'Online'
-			elif bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_OFFLINE:
-				status = 'Offline'
-			else:
-				status = 'Unknown'
-		return status
-	
-	def onBuddyState(self):
-		self.app.updateBuddy(self)
+    def statusText(self):
+        bi = self.getInfo()
+        status = ''
+        if bi.subState == pj.PJSIP_EVSUB_STATE_ACTIVE:
+            if bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE:
+                status = bi.presStatus.statusText
+                if not status:
+                    status = 'Online'
+            elif bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_OFFLINE:
+                status = 'Offline'
+            else:
+                status = 'Unknown'
+        return status
+
+    def onBuddyState(self):
+        self.app.updateBuddy(self)
 
 class SettingDialog(tk.Toplevel):
-	"""
-	This implements buddy settings dialog to manipulate buddy settings.
-	"""
-	def __init__(self, parent, cfg):
-		tk.Toplevel.__init__(self, parent)
-		self.transient(parent)
-		self.parent = parent
-		self.geometry("+100+100")
-		self.title('Buddy settings')
-		
-		self.frm = ttk.Frame(self)
-		self.frm.pack(expand='yes', fill='both')
-		
-		self.isOk = False
-		self.cfg = cfg
-		
-		self.createWidgets()
-	
-	def doModal(self):
-		if self.parent:
-			self.parent.wait_window(self)
-		else:
-			self.wait_window(self)
-		return self.isOk
-		
-	def createWidgets(self):
-		# The notebook
-		self.frm.rowconfigure(0, weight=1)
-		self.frm.rowconfigure(1, weight=0)
-		self.frm.columnconfigure(0, weight=1)
-		self.frm.columnconfigure(1, weight=1)
-		self.wTab = ttk.Notebook(self.frm)
-		self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E)
-		
-		# Main buttons
-		btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
-		btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
-		btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
-		btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
-		
-		# Tabs
-		self.createBasicTab()
-		
-	def createBasicTab(self):
-		# Prepare the variables to set/receive values from GUI
-		self.cfgUri = tk.StringVar()
-		self.cfgUri.set( self.cfg.uri )
-		self.cfgSubscribe = tk.IntVar()
-		self.cfgSubscribe.set(self.cfg.subscribe)
-		
-		# Build the tab page
-		frm = ttk.Frame(self.frm)
-		frm.columnconfigure(0, weight=1)
-		frm.columnconfigure(1, weight=2)
-		row = 0
-		ttk.Label(frm, text='URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
-		ttk.Entry(frm, textvariable=self.cfgUri, width=40).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
-		row += 1
-		ttk.Checkbutton(frm, text='Subscribe presence', variable=self.cfgSubscribe).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+    """
+    This implements buddy settings dialog to manipulate buddy settings.
+    """
+    def __init__(self, parent, cfg):
+        tk.Toplevel.__init__(self, parent)
+        self.transient(parent)
+        self.parent = parent
+        self.geometry("+100+100")
+        self.title('Buddy settings')
 
-		self.wTab.add(frm, text='Basic Settings')
-		
-	
-	def onOk(self):
-		# Check basic settings
-		errors = "";
-		if self.cfgUri.get():
-			if not endpoint.validateSipUri(self.cfgUri.get()):
-				errors += "Invalid Buddy URI: '%s'\n" % (self.cfgUri.get())
-				
-		if errors:
-			msgbox.showerror("Error detected:", errors)
-			return
-		
-		# Basic settings
-		self.cfg.uri = self.cfgUri.get()
-		self.cfg.subscribe = self.cfgSubscribe.get()
-		
-		self.isOk = True
-		self.destroy()
-		
-	def onCancel(self):
-		self.destroy()
+        self.frm = ttk.Frame(self)
+        self.frm.pack(expand='yes', fill='both')
+
+        self.isOk = False
+        self.cfg = cfg
+
+        self.createWidgets()
+
+    def doModal(self):
+        if self.parent:
+            self.parent.wait_window(self)
+        else:
+            self.wait_window(self)
+        return self.isOk
+
+    def createWidgets(self):
+        # The notebook
+        self.frm.rowconfigure(0, weight=1)
+        self.frm.rowconfigure(1, weight=0)
+        self.frm.columnconfigure(0, weight=1)
+        self.frm.columnconfigure(1, weight=1)
+        self.wTab = ttk.Notebook(self.frm)
+        self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E)
+
+        # Main buttons
+        btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
+        btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
+        btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
+        btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
+
+        # Tabs
+        self.createBasicTab()
+
+    def createBasicTab(self):
+        # Prepare the variables to set/receive values from GUI
+        self.cfgUri = tk.StringVar()
+        self.cfgUri.set( self.cfg.uri )
+        self.cfgSubscribe = tk.IntVar()
+        self.cfgSubscribe.set(self.cfg.subscribe)
+
+        # Build the tab page
+        frm = ttk.Frame(self.frm)
+        frm.columnconfigure(0, weight=1)
+        frm.columnconfigure(1, weight=2)
+        row = 0
+        ttk.Label(frm, text='URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
+        ttk.Entry(frm, textvariable=self.cfgUri, width=40).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
+        row += 1
+        ttk.Checkbutton(frm, text='Subscribe presence', variable=self.cfgSubscribe).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
+
+        self.wTab.add(frm, text='Basic Settings')
+
+
+    def onOk(self):
+        # Check basic settings
+        errors = "";
+        if self.cfgUri.get():
+            if not endpoint.validateSipUri(self.cfgUri.get()):
+                errors += "Invalid Buddy URI: '%s'\n" % (self.cfgUri.get())
+
+        if errors:
+            msgbox.showerror("Error detected:", errors)
+            return
+
+        # Basic settings
+        self.cfg.uri = self.cfgUri.get()
+        self.cfg.subscribe = self.cfgSubscribe.get()
+
+        self.isOk = True
+        self.destroy()
+
+    def onCancel(self):
+        self.destroy()
 
 
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/call.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/call.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/call.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import random
@@ -36,71 +36,71 @@
 # Call class
 class Call(pj.Call):
-	"""
-	High level Python Call object, derived from pjsua2's Call object.
-	"""
-	def __init__(self, acc, peer_uri='', chat=None, call_id = pj.PJSUA_INVALID_ID):
-		pj.Call.__init__(self, acc, call_id)
-                self.acc = acc
-		self.peerUri = peer_uri
-		self.chat = chat
-		self.connected = False
-		self.onhold = False
+    """
+    High level Python Call object, derived from pjsua2's Call object.
+    """
+    def __init__(self, acc, peer_uri='', chat=None, call_id = pj.PJSUA_INVALID_ID):
+        pj.Call.__init__(self, acc, call_id)
+        self.acc = acc
+        self.peerUri = peer_uri
+        self.chat = chat
+        self.connected = False
+        self.onhold = False
 
-	def onCallState(self, prm):
-		ci = self.getInfo()
-		self.connected = ci.state == pj.PJSIP_INV_STATE_CONFIRMED			
-		if self.chat:
-			self.chat.updateCallState(self, ci)
-			
-	def onCallMediaState(self, prm):
-		ci = self.getInfo()
-		for mi in ci.media:
-			if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
-			  (mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE or \
-			   mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD):
-				m = self.getMedia(mi.index)
-				am = pj.AudioMedia.typecastFromMedia(m)
-				# connect ports
-				ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
-				am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
+    def onCallState(self, prm):
+        ci = self.getInfo()
+        self.connected = ci.state == pj.PJSIP_INV_STATE_CONFIRMED
+        if self.chat:
+            self.chat.updateCallState(self, ci)
 
-				if mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD and not self.onhold:
-					self.chat.addMessage(None, "'%s' sets call onhold" % (self.peerUri))
-					self.onhold = True
-				elif mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE and self.onhold:
-					self.chat.addMessage(None, "'%s' sets call active" % (self.peerUri))
-					self.onhold = False
-		if self.chat:
-			self.chat.updateCallMediaState(self, ci)
-			
-	def onInstantMessage(self, prm):
-		# chat instance should have been initalized
-		if not self.chat: return
-			
-		self.chat.addMessage(self.peerUri, prm.msgBody)
-		self.chat.showWindow()
-			
-	def onInstantMessageStatus(self, prm):
-		if prm.code/100 == 2: return
-		# chat instance should have been initalized
-		if not self.chat: return
-		
-		self.chat.addMessage(None, "Failed sending message to '%s' (%d): %s" % (self.peerUri, prm.code, prm.reason))
-		
-	def onTypingIndication(self, prm):
-		# chat instance should have been initalized
-		if not self.chat: return
-		
-		self.chat.setTypingIndication(self.peerUri, prm.isTyping)
-	
-	def onDtmfDigit(self, prm):
-		#msgbox.showinfo("pygui", 'Got DTMF:' + prm.digit)
-		pass
-		
-	def onCallMediaTransportState(self, prm):
-		#msgbox.showinfo("pygui", "Media transport state")
-		pass
-		
-		
+    def onCallMediaState(self, prm):
+        ci = self.getInfo()
+        for mi in ci.media:
+            if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
+              (mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE or \
+               mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD):
+                m = self.getMedia(mi.index)
+                am = pj.AudioMedia.typecastFromMedia(m)
+                # connect ports
+                ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
+                am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
+
+                if mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD and not self.onhold:
+                    self.chat.addMessage(None, "'%s' sets call onhold" % (self.peerUri))
+                    self.onhold = True
+                elif mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE and self.onhold:
+                    self.chat.addMessage(None, "'%s' sets call active" % (self.peerUri))
+                    self.onhold = False
+        if self.chat:
+            self.chat.updateCallMediaState(self, ci)
+
+    def onInstantMessage(self, prm):
+        # chat instance should have been initalized
+        if not self.chat: return
+
+        self.chat.addMessage(self.peerUri, prm.msgBody)
+        self.chat.showWindow()
+
+    def onInstantMessageStatus(self, prm):
+        if prm.code/100 == 2: return
+        # chat instance should have been initalized
+        if not self.chat: return
+
+        self.chat.addMessage(None, "Failed sending message to '%s' (%d): %s" % (self.peerUri, prm.code, prm.reason))
+
+    def onTypingIndication(self, prm):
+        # chat instance should have been initalized
+        if not self.chat: return
+
+        self.chat.setTypingIndication(self.peerUri, prm.isTyping)
+
+    def onDtmfDigit(self, prm):
+        #msgbox.showinfo("pygui", 'Got DTMF:' + prm.digit)
+        pass
+
+    def onCallMediaTransportState(self, prm):
+        #msgbox.showinfo("pygui", "Media transport state")
+        pass
+
+
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/chat.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/chat.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/chat.py	(revision 5638)
@@ -21,9 +21,9 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
+    import tkinter as tk
+    from tkinter import ttk
 else:
-	import Tkinter as tk
-	import ttk
+    import Tkinter as tk
+    import ttk
 
 import buddy
@@ -36,509 +36,510 @@
 SipUriRegex = re.compile('(sip|sips):([^:;>\@]*)@?([^:;>]*):?([^:;>]*)')
 ConfIdx = 1
+write=sys.stdout.write
 
 # Simple SIP uri parser, input URI must have been validated
 def ParseSipUri(sip_uri_str):
-	m = SipUriRegex.search(sip_uri_str)
-	if not m:
-		assert(0)
-		return None
-	
-	scheme = m.group(1)
-	user = m.group(2)
-	host = m.group(3)
-	port = m.group(4)
-	if host == '':
-		host = user
-		user = ''
-		
-	return SipUri(scheme.lower(), user, host.lower(), port)
-	
+    m = SipUriRegex.search(sip_uri_str)
+    if not m:
+        assert(0)
+        return None
+
+    scheme = m.group(1)
+    user = m.group(2)
+    host = m.group(3)
+    port = m.group(4)
+    if host == '':
+        host = user
+        user = ''
+
+    return SipUri(scheme.lower(), user, host.lower(), port)
+
 class SipUri:
-	def __init__(self, scheme, user, host, port):
-		self.scheme = scheme
-		self.user = user
-		self.host = host
-		self.port = port
-		
-	def __cmp__(self, sip_uri):
-		if self.scheme == sip_uri.scheme and self.user == sip_uri.user and self.host == sip_uri.host:
-			# don't check port, at least for now
-			return 0
-		return -1
-	
-	def __str__(self):
-		s = self.scheme + ':'
-		if self.user: s += self.user + '@'
-		s += self.host
-		if self.port: s+= ':' + self.port
-		return s
-	
+    def __init__(self, scheme, user, host, port):
+        self.scheme = scheme
+        self.user = user
+        self.host = host
+        self.port = port
+
+    def __cmp__(self, sip_uri):
+        if self.scheme == sip_uri.scheme and self.user == sip_uri.user and self.host == sip_uri.host:
+            # don't check port, at least for now
+            return 0
+        return -1
+
+    def __str__(self):
+        s = self.scheme + ':'
+        if self.user: s += self.user + '@'
+        s += self.host
+        if self.port: s+= ':' + self.port
+        return s
+
 class Chat(gui.ChatObserver):
-	def __init__(self, app, acc, uri, call_inst=None):
-		self._app = app
-		self._acc = acc
-		self.title = ''
-		
-		global ConfIdx
-		self.confIdx = ConfIdx
-		ConfIdx += 1
-		
-		# each participant call/buddy instances are stored in call list
-		# and buddy list with same index as in particpant list
-		self._participantList = []	# list of SipUri
-		self._callList = []		# list of Call
-		self._buddyList = []		# list of Buddy
-		
-		self._gui = gui.ChatFrame(self)
-		self.addParticipant(uri, call_inst)
-	
-	def _updateGui(self):
-		if self.isPrivate():
-			self.title = str(self._participantList[0])
-		else:
-			self.title = 'Conference #%d (%d participants)' % (self.confIdx, len(self._participantList))
-		self._gui.title(self.title)
-		self._app.updateWindowMenu()
-		
-	def _getCallFromUriStr(self, uri_str, op = ''):
-		uri = ParseSipUri(uri_str)
-		if uri not in self._participantList:
-			print "=== %s cannot find participant with URI '%s'" % (op, uri_str)
-			return None
-		idx = self._participantList.index(uri)
-		if idx < len(self._callList):
-			return self._callList[idx]
-		return None
-	
-	def _getActiveMediaIdx(self, thecall):
-		ci = thecall.getInfo()
-		for mi in ci.media:
-			if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
-			  (mi.status != pj.PJSUA_CALL_MEDIA_NONE and \
-			   mi.status != pj.PJSUA_CALL_MEDIA_ERROR):
-				return mi.index
-		return -1
-		
-	def _getAudioMediaFromUriStr(self, uri_str):
-		c = self._getCallFromUriStr(uri_str)
-		if not c: return None
-
-		idx = self._getActiveMediaIdx(c)
-		if idx < 0: return None
-
-		m = c.getMedia(idx)
-		am = pj.AudioMedia.typecastFromMedia(m)
-		return am
-		
-	def _sendTypingIndication(self, is_typing, sender_uri_str=''):
-		sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
-		type_ind_param = pj.SendTypingIndicationParam()
-		type_ind_param.isTyping = is_typing
-		for idx, p in enumerate(self._participantList):
-			# don't echo back to the original sender
-			if sender_uri and p == sender_uri:
-				continue
-				
-			# send via call, if any, or buddy
-			target = None
-			if self._callList[idx] and self._callList[idx].connected:
-				target = self._callList[idx]
-			else:
-				target = self._buddyList[idx]
-			assert(target)
-				
-			try:
-				target.sendTypingIndication(type_ind_param)
-			except:
-				pass
-
-	def _sendInstantMessage(self, msg, sender_uri_str=''):
-		sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
-		send_im_param = pj.SendInstantMessageParam()
-		send_im_param.content = str(msg)
-		for idx, p in enumerate(self._participantList):
-			# don't echo back to the original sender
-			if sender_uri and p == sender_uri:
-				continue
-				
-			# send via call, if any, or buddy
-			target = None
-			if self._callList[idx] and self._callList[idx].connected:
-				target = self._callList[idx]
-			else:
-				target = self._buddyList[idx]
-			assert(target)
-			
-			try:
-				target.sendInstantMessage(send_im_param)
-			except:
-				# error will be handled via Account::onInstantMessageStatus()
-				pass
-
-	def isPrivate(self):
-		return len(self._participantList) <= 1
-		
-	def isUriParticipant(self, uri):
-		return uri in self._participantList
-		
-	def registerCall(self, uri_str, call_inst):
-		uri = ParseSipUri(uri_str)
-		try:
-			idx = self._participantList.index(uri)
-			bud = self._buddyList[idx]
-			self._callList[idx] = call_inst
-			call_inst.chat = self
-			call_inst.peerUri = bud.cfg.uri
-		except:
-			assert(0) # idx must be found!
-		
-	def showWindow(self, show_text_chat = False):
-		self._gui.bringToFront()
-		if show_text_chat:
-			self._gui.textShowHide(True)
-		
-	def addParticipant(self, uri, call_inst=None):
-		# avoid duplication
-		if self.isUriParticipant(uri): return
-		
-		uri_str = str(uri)
-		
-		# find buddy, create one if not found (e.g: for IM/typing ind),
-		# it is a temporary one and not really registered to acc
-		bud = None
-		try:
-			bud = self._acc.findBuddy(uri_str)
-		except:
-			bud = buddy.Buddy(None)
-			bud_cfg = pj.BuddyConfig()
-			bud_cfg.uri = uri_str
-			bud_cfg.subscribe = False
-			bud.create(self._acc, bud_cfg)
-			bud.cfg = bud_cfg
-			bud.account = self._acc
-			
-		# update URI from buddy URI
-		uri = ParseSipUri(bud.cfg.uri)
-		
-		# add it
-		self._participantList.append(uri)
-		self._callList.append(call_inst)
-		self._buddyList.append(bud)
-		self._gui.addParticipant(str(uri))
-		self._updateGui()
-	
-	def kickParticipant(self, uri):
-		if (not uri) or (uri not in self._participantList):
-			assert(0)
-			return
-		
-		idx = self._participantList.index(uri)
-		del self._participantList[idx]
-		del self._callList[idx]
-		del self._buddyList[idx]
-		self._gui.delParticipant(str(uri))
-		
-		if self._participantList:
-			self._updateGui()
-		else:
-			self.onCloseWindow()
-			
-	def addMessage(self, from_uri_str, msg):
-		if from_uri_str:
-			# print message on GUI
-			msg = from_uri_str + ': ' + msg
-			self._gui.textAddMessage(msg)
-			# now relay to all participants
-			self._sendInstantMessage(msg, from_uri_str)
-		else:
-			self._gui.textAddMessage(msg, False)
-			
-	def setTypingIndication(self, from_uri_str, is_typing):
-		# notify GUI
-		self._gui.textSetTypingIndication(from_uri_str, is_typing)
-		# now relay to all participants
-		self._sendTypingIndication(is_typing, from_uri_str)
-		
-	def startCall(self):
-		self._gui.enableAudio()
-		call_param = pj.CallOpParam()
-		call_param.opt.audioCount = 1
-		call_param.opt.videoCount = 0
-		fails = []
-		for idx, p in enumerate(self._participantList):
-			# just skip if call is instantiated
-			if self._callList[idx]:
-				continue
-			
-			uri_str = str(p)
-			c = call.Call(self._acc, uri_str, self)
-			self._callList[idx] = c
-			self._gui.audioUpdateState(uri_str, gui.AudioState.INITIALIZING)
-			
-			try:
-				c.makeCall(uri_str, call_param)
-			except:
-				self._callList[idx] = None
-				self._gui.audioUpdateState(uri_str, gui.AudioState.FAILED)
-				fails.append(p)
-				
-		for p in fails:
-			# kick participants with call failure, but spare the last (avoid zombie chat)
-			if not self.isPrivate():
-				self.kickParticipant(p)
-			
-	def stopCall(self):
-		for idx, p in enumerate(self._participantList):
-			self._gui.audioUpdateState(str(p), gui.AudioState.DISCONNECTED)
-			c = self._callList[idx]
-			if c:
-				c.hangup(pj.CallOpParam())
-
-	def updateCallState(self, thecall, info = None):
-		# info is optional here, just to avoid calling getInfo() twice (in the caller and here)
-		if not info: info = thecall.getInfo()
-		
-		if info.state < pj.PJSIP_INV_STATE_CONFIRMED:
-			self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.INITIALIZING)
-		elif info.state == pj.PJSIP_INV_STATE_CONFIRMED:
-			self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.CONNECTED)
-			if not self.isPrivate():
-				# inform peer about conference participants
-				conf_welcome_str  = '\n---\n'
-				conf_welcome_str += 'Welcome to the conference, participants:\n'
-				conf_welcome_str += '%s (host)\n' % (self._acc.cfg.idUri)
-				for p in self._participantList:
-					conf_welcome_str += '%s\n' % (str(p))
-				conf_welcome_str += '---\n'
-				send_im_param = pj.SendInstantMessageParam()
-				send_im_param.content = conf_welcome_str
-				try:
-					thecall.sendInstantMessage(send_im_param)
-				except:
-					pass
-					
-				# inform others, including self
-				msg = "[Conf manager] %s has joined" % (thecall.peerUri)
-				self.addMessage(None, msg)
-				self._sendInstantMessage(msg, thecall.peerUri)
-				
-		elif info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
-			if info.lastStatusCode/100 != 2:
-				self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.FAILED)
-			else:
-				self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.DISCONNECTED)
-			
-			# reset entry in the callList
-			try:
-				idx = self._callList.index(thecall)
-				if idx >= 0: self._callList[idx] = None
-			except:
-				pass
-			
-			self.addMessage(None, "Call to '%s' disconnected: %s" % (thecall.peerUri, info.lastReason))
-			
-			# kick the disconnected participant, but the last (avoid zombie chat)
-			if not self.isPrivate():
-				self.kickParticipant(ParseSipUri(thecall.peerUri))
-				
-				# inform others, including self
-				msg = "[Conf manager] %s has left" % (thecall.peerUri)
-				self.addMessage(None, msg)
-				self._sendInstantMessage(msg, thecall.peerUri)
-
-	def updateCallMediaState(self, thecall, info = None):
-		# info is optional here, just to avoid calling getInfo() twice (in the caller and here)
-		if not info: info = thecall.getInfo()
-		
-		med_idx = self._getActiveMediaIdx(thecall)
-		if (med_idx < 0):
-			self._gui.audioSetStatsText(thecall.peerUri, 'No active media')
-			return
-
-		si = thecall.getStreamInfo(med_idx)
-		dir_str = ''
-		if si.dir == 0:
-			dir_str = 'inactive'
-		else:
-			if si.dir & pj.PJMEDIA_DIR_ENCODING:
-				dir_str += 'send '
-			if si.dir & pj.PJMEDIA_DIR_DECODING:
-				dir_str += 'receive '
-		stats_str  = "Direction   : %s\n" % (dir_str)
-		stats_str += "Audio codec : %s (%sHz)" % (si.codecName, si.codecClockRate)
-		self._gui.audioSetStatsText(thecall.peerUri, stats_str)
-		m = pj.AudioMedia.typecastFromMedia(thecall.getMedia(med_idx))
-		
-		# make conference
-		for c in self._callList:
-			if c == thecall:
-				continue
-			med_idx = self._getActiveMediaIdx(c)
-			if med_idx < 0:
-				continue
-			mm = pj.AudioMedia.typecastFromMedia(c.getMedia(med_idx))
-			m.startTransmit(mm)
-			mm.startTransmit(m)
-
-			
-	# ** callbacks from GUI (ChatObserver implementation) **
-	
-	# Text
-	def onSendMessage(self, msg):
-		self._sendInstantMessage(msg)
-
-	def onStartTyping(self):
-		self._sendTypingIndication(True)
-		
-	def onStopTyping(self):
-		self._sendTypingIndication(False)
-		
-	# Audio
-	def onHangup(self, peer_uri_str):
-		c = self._getCallFromUriStr(peer_uri_str, "onHangup()")
-		if not c: return
-		call_param = pj.CallOpParam()
-		c.hangup(call_param)
-
-	def onHold(self, peer_uri_str):
-		c = self._getCallFromUriStr(peer_uri_str, "onHold()")
-		if not c: return
-		call_param = pj.CallOpParam()
-		c.setHold(call_param)
-
-	def onUnhold(self, peer_uri_str):
-		c = self._getCallFromUriStr(peer_uri_str, "onUnhold()")
-		if not c: return
-		
-		call_param = pj.CallOpParam()
-		call_param.opt.audioCount = 1
-		call_param.opt.videoCount = 0
-		call_param.opt.flag |= pj.PJSUA_CALL_UNHOLD
-		c.reinvite(call_param)
-		
-	def onRxMute(self, peer_uri_str, mute):
-		am = self._getAudioMediaFromUriStr(peer_uri_str)
-		if not am: return
-		if mute:
-			am.stopTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
-			self.addMessage(None, "Muted audio from '%s'" % (peer_uri_str))
-		else:
-			am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
-			self.addMessage(None, "Unmuted audio from '%s'" % (peer_uri_str))
-		
-	def onRxVol(self, peer_uri_str, vol_pct):
-		am = self._getAudioMediaFromUriStr(peer_uri_str)
-		if not am: return
-		# pjsua volume range = 0:mute, 1:no adjustment, 2:100% louder
-		am.adjustRxLevel(vol_pct/50.0)
-		self.addMessage(None, "Adjusted volume level audio from '%s'" % (peer_uri_str))
-			
-	def onTxMute(self, peer_uri_str, mute):
-		am = self._getAudioMediaFromUriStr(peer_uri_str)
-		if not am: return
-		if mute:
-			ep.Endpoint.instance.audDevManager().getCaptureDevMedia().stopTransmit(am)
-			self.addMessage(None, "Muted audio to '%s'" % (peer_uri_str))
-		else:
-			ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
-			self.addMessage(None, "Unmuted audio to '%s'" % (peer_uri_str))
-
-	# Chat room
-	def onAddParticipant(self):
-		buds = []
-		dlg = AddParticipantDlg(None, self._app, buds)
-		if dlg.doModal():
-			for bud in buds:
-				uri = ParseSipUri(bud.cfg.uri)
-				self.addParticipant(uri)
-			if not self.isPrivate():
-				self.startCall()
-				
-	def onStartAudio(self):
-		self.startCall()
-
-	def onStopAudio(self):
-		self.stopCall()
-		
-	def onCloseWindow(self):
-		self.stopCall()
-		# will remove entry from list eventually destroy this chat?
-		if self in self._acc.chatList: self._acc.chatList.remove(self)
-		self._app.updateWindowMenu()
-		# destroy GUI
-		self._gui.destroy()
+    def __init__(self, app, acc, uri, call_inst=None):
+        self._app = app
+        self._acc = acc
+        self.title = ''
+
+        global ConfIdx
+        self.confIdx = ConfIdx
+        ConfIdx += 1
+
+        # each participant call/buddy instances are stored in call list
+        # and buddy list with same index as in particpant list
+        self._participantList = []	# list of SipUri
+        self._callList = []		# list of Call
+        self._buddyList = []		# list of Buddy
+
+        self._gui = gui.ChatFrame(self)
+        self.addParticipant(uri, call_inst)
+
+    def _updateGui(self):
+        if self.isPrivate():
+            self.title = str(self._participantList[0])
+        else:
+            self.title = 'Conference #%d (%d participants)' % (self.confIdx, len(self._participantList))
+        self._gui.title(self.title)
+        self._app.updateWindowMenu()
+
+    def _getCallFromUriStr(self, uri_str, op = ''):
+        uri = ParseSipUri(uri_str)
+        if uri not in self._participantList:
+            write("=== "+ op +" cannot find participant with URI '" + uri_str + "'\r\n")
+            return None
+        idx = self._participantList.index(uri)
+        if idx < len(self._callList):
+            return self._callList[idx]
+        return None
+
+    def _getActiveMediaIdx(self, thecall):
+        ci = thecall.getInfo()
+        for mi in ci.media:
+            if mi.type == pj.PJMEDIA_TYPE_AUDIO and \
+              (mi.status != pj.PJSUA_CALL_MEDIA_NONE and \
+               mi.status != pj.PJSUA_CALL_MEDIA_ERROR):
+                return mi.index
+        return -1
+
+    def _getAudioMediaFromUriStr(self, uri_str):
+        c = self._getCallFromUriStr(uri_str)
+        if not c: return None
+
+        idx = self._getActiveMediaIdx(c)
+        if idx < 0: return None
+
+        m = c.getMedia(idx)
+        am = pj.AudioMedia.typecastFromMedia(m)
+        return am
+
+    def _sendTypingIndication(self, is_typing, sender_uri_str=''):
+        sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
+        type_ind_param = pj.SendTypingIndicationParam()
+        type_ind_param.isTyping = is_typing
+        for idx, p in enumerate(self._participantList):
+            # don't echo back to the original sender
+            if sender_uri and p == sender_uri:
+                continue
+
+            # send via call, if any, or buddy
+            target = None
+            if self._callList[idx] and self._callList[idx].connected:
+                target = self._callList[idx]
+            else:
+                target = self._buddyList[idx]
+            assert(target)
+
+            try:
+                target.sendTypingIndication(type_ind_param)
+            except:
+                pass
+
+    def _sendInstantMessage(self, msg, sender_uri_str=''):
+        sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None
+        send_im_param = pj.SendInstantMessageParam()
+        send_im_param.content = str(msg)
+        for idx, p in enumerate(self._participantList):
+            # don't echo back to the original sender
+            if sender_uri and p == sender_uri:
+                continue
+
+            # send via call, if any, or buddy
+            target = None
+            if self._callList[idx] and self._callList[idx].connected:
+                target = self._callList[idx]
+            else:
+                target = self._buddyList[idx]
+            assert(target)
+
+            try:
+                target.sendInstantMessage(send_im_param)
+            except:
+                # error will be handled via Account::onInstantMessageStatus()
+                pass
+
+    def isPrivate(self):
+        return len(self._participantList) <= 1
+
+    def isUriParticipant(self, uri):
+        return uri in self._participantList
+
+    def registerCall(self, uri_str, call_inst):
+        uri = ParseSipUri(uri_str)
+        try:
+            idx = self._participantList.index(uri)
+            bud = self._buddyList[idx]
+            self._callList[idx] = call_inst
+            call_inst.chat = self
+            call_inst.peerUri = bud.cfg.uri
+        except:
+            assert(0) # idx must be found!
+
+    def showWindow(self, show_text_chat = False):
+        self._gui.bringToFront()
+        if show_text_chat:
+            self._gui.textShowHide(True)
+
+    def addParticipant(self, uri, call_inst=None):
+        # avoid duplication
+        if self.isUriParticipant(uri): return
+
+        uri_str = str(uri)
+
+        # find buddy, create one if not found (e.g: for IM/typing ind),
+        # it is a temporary one and not really registered to acc
+        bud = None
+        try:
+            bud = self._acc.findBuddy(uri_str)
+        except:
+            bud = buddy.Buddy(None)
+            bud_cfg = pj.BuddyConfig()
+            bud_cfg.uri = uri_str
+            bud_cfg.subscribe = False
+            bud.create(self._acc, bud_cfg)
+            bud.cfg = bud_cfg
+            bud.account = self._acc
+
+        # update URI from buddy URI
+        uri = ParseSipUri(bud.cfg.uri)
+
+        # add it
+        self._participantList.append(uri)
+        self._callList.append(call_inst)
+        self._buddyList.append(bud)
+        self._gui.addParticipant(str(uri))
+        self._updateGui()
+
+    def kickParticipant(self, uri):
+        if (not uri) or (uri not in self._participantList):
+            assert(0)
+            return
+
+        idx = self._participantList.index(uri)
+        del self._participantList[idx]
+        del self._callList[idx]
+        del self._buddyList[idx]
+        self._gui.delParticipant(str(uri))
+
+        if self._participantList:
+            self._updateGui()
+        else:
+            self.onCloseWindow()
+
+    def addMessage(self, from_uri_str, msg):
+        if from_uri_str:
+            # print message on GUI
+            msg = from_uri_str + ': ' + msg
+            self._gui.textAddMessage(msg)
+            # now relay to all participants
+            self._sendInstantMessage(msg, from_uri_str)
+        else:
+            self._gui.textAddMessage(msg, False)
+
+    def setTypingIndication(self, from_uri_str, is_typing):
+        # notify GUI
+        self._gui.textSetTypingIndication(from_uri_str, is_typing)
+        # now relay to all participants
+        self._sendTypingIndication(is_typing, from_uri_str)
+
+    def startCall(self):
+        self._gui.enableAudio()
+        call_param = pj.CallOpParam()
+        call_param.opt.audioCount = 1
+        call_param.opt.videoCount = 0
+        fails = []
+        for idx, p in enumerate(self._participantList):
+            # just skip if call is instantiated
+            if self._callList[idx]:
+                continue
+
+            uri_str = str(p)
+            c = call.Call(self._acc, uri_str, self)
+            self._callList[idx] = c
+            self._gui.audioUpdateState(uri_str, gui.AudioState.INITIALIZING)
+
+            try:
+                c.makeCall(uri_str, call_param)
+            except:
+                self._callList[idx] = None
+                self._gui.audioUpdateState(uri_str, gui.AudioState.FAILED)
+                fails.append(p)
+
+        for p in fails:
+            # kick participants with call failure, but spare the last (avoid zombie chat)
+            if not self.isPrivate():
+                self.kickParticipant(p)
+
+    def stopCall(self):
+        for idx, p in enumerate(self._participantList):
+            self._gui.audioUpdateState(str(p), gui.AudioState.DISCONNECTED)
+            c = self._callList[idx]
+            if c:
+                c.hangup(pj.CallOpParam())
+
+    def updateCallState(self, thecall, info = None):
+        # info is optional here, just to avoid calling getInfo() twice (in the caller and here)
+        if not info: info = thecall.getInfo()
+
+        if info.state < pj.PJSIP_INV_STATE_CONFIRMED:
+            self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.INITIALIZING)
+        elif info.state == pj.PJSIP_INV_STATE_CONFIRMED:
+            self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.CONNECTED)
+            if not self.isPrivate():
+                # inform peer about conference participants
+                conf_welcome_str  = '\n---\n'
+                conf_welcome_str += 'Welcome to the conference, participants:\n'
+                conf_welcome_str += '%s (host)\n' % (self._acc.cfg.idUri)
+                for p in self._participantList:
+                    conf_welcome_str += '%s\n' % (str(p))
+                conf_welcome_str += '---\n'
+                send_im_param = pj.SendInstantMessageParam()
+                send_im_param.content = conf_welcome_str
+                try:
+                    thecall.sendInstantMessage(send_im_param)
+                except:
+                    pass
+
+                # inform others, including self
+                msg = "[Conf manager] %s has joined" % (thecall.peerUri)
+                self.addMessage(None, msg)
+                self._sendInstantMessage(msg, thecall.peerUri)
+
+        elif info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
+            if info.lastStatusCode/100 != 2:
+                self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.FAILED)
+            else:
+                self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.DISCONNECTED)
+
+            # reset entry in the callList
+            try:
+                idx = self._callList.index(thecall)
+                if idx >= 0: self._callList[idx] = None
+            except:
+                pass
+
+            self.addMessage(None, "Call to '%s' disconnected: %s" % (thecall.peerUri, info.lastReason))
+
+            # kick the disconnected participant, but the last (avoid zombie chat)
+            if not self.isPrivate():
+                self.kickParticipant(ParseSipUri(thecall.peerUri))
+
+                # inform others, including self
+                msg = "[Conf manager] %s has left" % (thecall.peerUri)
+                self.addMessage(None, msg)
+                self._sendInstantMessage(msg, thecall.peerUri)
+
+    def updateCallMediaState(self, thecall, info = None):
+        # info is optional here, just to avoid calling getInfo() twice (in the caller and here)
+        if not info: info = thecall.getInfo()
+
+        med_idx = self._getActiveMediaIdx(thecall)
+        if (med_idx < 0):
+            self._gui.audioSetStatsText(thecall.peerUri, 'No active media')
+            return
+
+        si = thecall.getStreamInfo(med_idx)
+        dir_str = ''
+        if si.dir == 0:
+            dir_str = 'inactive'
+        else:
+            if si.dir & pj.PJMEDIA_DIR_ENCODING:
+                dir_str += 'send '
+            if si.dir & pj.PJMEDIA_DIR_DECODING:
+                dir_str += 'receive '
+        stats_str  = "Direction   : %s\n" % (dir_str)
+        stats_str += "Audio codec : %s (%sHz)" % (si.codecName, si.codecClockRate)
+        self._gui.audioSetStatsText(thecall.peerUri, stats_str)
+        m = pj.AudioMedia.typecastFromMedia(thecall.getMedia(med_idx))
+
+        # make conference
+        for c in self._callList:
+            if c == thecall:
+                continue
+            med_idx = self._getActiveMediaIdx(c)
+            if med_idx < 0:
+                continue
+            mm = pj.AudioMedia.typecastFromMedia(c.getMedia(med_idx))
+            m.startTransmit(mm)
+            mm.startTransmit(m)
+
+
+    # ** callbacks from GUI (ChatObserver implementation) **
+
+    # Text
+    def onSendMessage(self, msg):
+        self._sendInstantMessage(msg)
+
+    def onStartTyping(self):
+        self._sendTypingIndication(True)
+
+    def onStopTyping(self):
+        self._sendTypingIndication(False)
+
+    # Audio
+    def onHangup(self, peer_uri_str):
+        c = self._getCallFromUriStr(peer_uri_str, "onHangup()")
+        if not c: return
+        call_param = pj.CallOpParam()
+        c.hangup(call_param)
+
+    def onHold(self, peer_uri_str):
+        c = self._getCallFromUriStr(peer_uri_str, "onHold()")
+        if not c: return
+        call_param = pj.CallOpParam()
+        c.setHold(call_param)
+
+    def onUnhold(self, peer_uri_str):
+        c = self._getCallFromUriStr(peer_uri_str, "onUnhold()")
+        if not c: return
+
+        call_param = pj.CallOpParam()
+        call_param.opt.audioCount = 1
+        call_param.opt.videoCount = 0
+        call_param.opt.flag |= pj.PJSUA_CALL_UNHOLD
+        c.reinvite(call_param)
+
+    def onRxMute(self, peer_uri_str, mute):
+        am = self._getAudioMediaFromUriStr(peer_uri_str)
+        if not am: return
+        if mute:
+            am.stopTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
+            self.addMessage(None, "Muted audio from '%s'" % (peer_uri_str))
+        else:
+            am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia())
+            self.addMessage(None, "Unmuted audio from '%s'" % (peer_uri_str))
+
+    def onRxVol(self, peer_uri_str, vol_pct):
+        am = self._getAudioMediaFromUriStr(peer_uri_str)
+        if not am: return
+        # pjsua volume range = 0:mute, 1:no adjustment, 2:100% louder
+        am.adjustRxLevel(vol_pct/50.0)
+        self.addMessage(None, "Adjusted volume level audio from '%s'" % (peer_uri_str))
+
+    def onTxMute(self, peer_uri_str, mute):
+        am = self._getAudioMediaFromUriStr(peer_uri_str)
+        if not am: return
+        if mute:
+            ep.Endpoint.instance.audDevManager().getCaptureDevMedia().stopTransmit(am)
+            self.addMessage(None, "Muted audio to '%s'" % (peer_uri_str))
+        else:
+            ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am)
+            self.addMessage(None, "Unmuted audio to '%s'" % (peer_uri_str))
+
+    # Chat room
+    def onAddParticipant(self):
+        buds = []
+        dlg = AddParticipantDlg(None, self._app, buds)
+        if dlg.doModal():
+            for bud in buds:
+                uri = ParseSipUri(bud.cfg.uri)
+                self.addParticipant(uri)
+            if not self.isPrivate():
+                self.startCall()
+
+    def onStartAudio(self):
+        self.startCall()
+
+    def onStopAudio(self):
+        self.stopCall()
+
+    def onCloseWindow(self):
+        self.stopCall()
+        # will remove entry from list eventually destroy this chat?
+        if self in self._acc.chatList: self._acc.chatList.remove(self)
+        self._app.updateWindowMenu()
+        # destroy GUI
+        self._gui.destroy()
 
 
 class AddParticipantDlg(tk.Toplevel):
-	"""
-	List of buddies
-	"""
-	def __init__(self, parent, app, bud_list):
-		tk.Toplevel.__init__(self, parent)
-		self.title('Add participants..')
-		self.transient(parent)
-		self.parent = parent
-		self._app = app
-		self.buddyList = bud_list
-		
-		self.isOk = False
-		
-		self.createWidgets()
-	
-	def doModal(self):
-		if self.parent:
-			self.parent.wait_window(self)
-		else:
-			self.wait_window(self)
-		return self.isOk
-		
-	def createWidgets(self):
-		# buddy list
-		list_frame = ttk.Frame(self)
-		list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1, padx=20, pady=20)
-		#scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=list_frame.yview)
-		#list_frame.config(yscrollcommand=scrl.set)
-		#scrl.pack(side=tk.RIGHT, fill=tk.Y)
-		
-		# draw buddy list
-		self.buddies = []
-		for acc in self._app.accList:
-			self.buddies.append((0, acc.cfg.idUri))
-			for bud in acc.buddyList:
-				self.buddies.append((1, bud))
-		
-		self.bud_var = []
-		for idx,(flag,bud) in enumerate(self.buddies):
-			self.bud_var.append(tk.IntVar())
-			if flag==0:
-				s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
-				s.pack(fill=tk.X)
-				l = tk.Label(list_frame, anchor=tk.W, text="Account '%s':" % (bud))
-				l.pack(fill=tk.X)
-			else:
-				c = tk.Checkbutton(list_frame, anchor=tk.W, text=bud.cfg.uri, variable=self.bud_var[idx])
-				c.pack(fill=tk.X)
-		s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
-		s.pack(fill=tk.X)
-
-		# Ok/cancel buttons
-		tail_frame = ttk.Frame(self)
-		tail_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
-		
-		btnOk = ttk.Button(tail_frame, text='Ok', default=tk.ACTIVE, command=self.onOk)
-		btnOk.pack(side=tk.LEFT, padx=20, pady=10)
-		btnCancel = ttk.Button(tail_frame, text='Cancel', command=self.onCancel)
-		btnCancel.pack(side=tk.RIGHT, padx=20, pady=10)
-		
-	def onOk(self):
-		self.buddyList[:] = []
-		for idx,(flag,bud) in enumerate(self.buddies):
-			if not flag: continue
-			if self.bud_var[idx].get() and not (bud in self.buddyList):
-				self.buddyList.append(bud)
-			
-		self.isOk = True
-		self.destroy()
-		
-	def onCancel(self):
-		self.destroy()
+    """
+    List of buddies
+    """
+    def __init__(self, parent, app, bud_list):
+        tk.Toplevel.__init__(self, parent)
+        self.title('Add participants..')
+        self.transient(parent)
+        self.parent = parent
+        self._app = app
+        self.buddyList = bud_list
+
+        self.isOk = False
+
+        self.createWidgets()
+
+    def doModal(self):
+        if self.parent:
+            self.parent.wait_window(self)
+        else:
+            self.wait_window(self)
+        return self.isOk
+
+    def createWidgets(self):
+        # buddy list
+        list_frame = ttk.Frame(self)
+        list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1, padx=20, pady=20)
+        #scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=list_frame.yview)
+        #list_frame.config(yscrollcommand=scrl.set)
+        #scrl.pack(side=tk.RIGHT, fill=tk.Y)
+
+        # draw buddy list
+        self.buddies = []
+        for acc in self._app.accList:
+            self.buddies.append((0, acc.cfg.idUri))
+            for bud in acc.buddyList:
+                self.buddies.append((1, bud))
+
+        self.bud_var = []
+        for idx,(flag,bud) in enumerate(self.buddies):
+            self.bud_var.append(tk.IntVar())
+            if flag==0:
+                s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
+                s.pack(fill=tk.X)
+                l = tk.Label(list_frame, anchor=tk.W, text="Account '%s':" % (bud))
+                l.pack(fill=tk.X)
+            else:
+                c = tk.Checkbutton(list_frame, anchor=tk.W, text=bud.cfg.uri, variable=self.bud_var[idx])
+                c.pack(fill=tk.X)
+        s = ttk.Separator(list_frame, orient=tk.HORIZONTAL)
+        s.pack(fill=tk.X)
+
+        # Ok/cancel buttons
+        tail_frame = ttk.Frame(self)
+        tail_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
+
+        btnOk = ttk.Button(tail_frame, text='Ok', default=tk.ACTIVE, command=self.onOk)
+        btnOk.pack(side=tk.LEFT, padx=20, pady=10)
+        btnCancel = ttk.Button(tail_frame, text='Cancel', command=self.onCancel)
+        btnCancel.pack(side=tk.RIGHT, padx=20, pady=10)
+
+    def onOk(self):
+        self.buddyList[:] = []
+        for idx,(flag,bud) in enumerate(self.buddies):
+            if not flag: continue
+            if self.bud_var[idx].get() and not (bud in self.buddyList):
+                self.buddyList.append(bud)
+
+        self.isOk = True
+        self.destroy()
+
+    def onCancel(self):
+        self.destroy()
Index: /pjproject/trunk/pjsip-apps/src/pygui/chatgui.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/chatgui.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/chatgui.py	(revision 5638)
@@ -21,400 +21,400 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    import tkinter as tk
+    from tkinter import ttk
+    from tkinter import messagebox as msgbox
 else:
-	import Tkinter as tk
-	import ttk
-	import tkMessageBox as msgbox
+    import Tkinter as tk
+    import ttk
+    import tkMessageBox as msgbox
 
 
 class TextObserver:
-	def onSendMessage(self, msg):
-		pass
-	def onStartTyping(self):
-		pass
-	def onStopTyping(self):
-		pass
-		
+    def onSendMessage(self, msg):
+        pass
+    def onStartTyping(self):
+        pass
+    def onStopTyping(self):
+        pass
+
 class TextFrame(ttk.Frame):
-	def __init__(self, master, observer):
-		ttk.Frame.__init__(self, master)
-		self._observer = observer
-		self._isTyping = False
-		self._createWidgets()
-
-	def _onSendMessage(self, event):
-		send_text = self._typingBox.get("1.0", tk.END).strip()
-		if send_text == '':
-			return
-		
-		self.addMessage('me: ' + send_text)
-		self._typingBox.delete("0.0", tk.END)
-		self._onTyping(None)
-		
-		# notify app for sending message
-		self._observer.onSendMessage(send_text)
-		
-	def _onTyping(self, event):
-		# notify app for typing indication
-		is_typing = self._typingBox.get("1.0", tk.END).strip() != ''
-		if is_typing != self._isTyping:
-			self._isTyping = is_typing
-			if is_typing:
-				self._observer.onStartTyping()
-			else:
-				self._observer.onStopTyping()
-		
-	def _createWidgets(self):
-		self.rowconfigure(0, weight=1)
-		self.rowconfigure(1, weight=0)
-		self.rowconfigure(2, weight=0)
-		self.columnconfigure(0, weight=1)
-		self.columnconfigure(1, weight=0)
-		
-		self._text = tk.Text(self, width=50, height=30, font=("Arial", "10"))
-		self._text.grid(row=0, column=0, sticky='nswe')
-		self._text.config(state=tk.DISABLED)
-		self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic"))
-		
-		scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview)
-		self._text.config(yscrollcommand=scrl.set)
-		scrl.grid(row=0, column=1, sticky='nsw')
-		
-		self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10"))
-		self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0)
-		
-		self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic"))
-		self._statusBar.grid(row=2, columnspan=2, sticky='we')
-		
-		self._typingBox.bind('<Return>', self._onSendMessage)
-		self._typingBox.bind("<Key>", self._onTyping)
-		self._typingBox.focus_set()
-		
-	def addMessage(self, msg, is_chat = True):
-		self._text.config(state=tk.NORMAL)
-		if is_chat:
-			self._text.insert(tk.END, msg+'\r\n')
-		else:
-			self._text.insert(tk.END, msg+'\r\n', 'info')
-		self._text.config(state=tk.DISABLED)
-		self._text.yview(tk.END)
-
-	def setTypingIndication(self, who, is_typing):
-		if is_typing:
-			self._statusBar['text'] = "'%s' is typing.." % (who)
-		else:
-			self._statusBar['text'] = ''
+    def __init__(self, master, observer):
+        ttk.Frame.__init__(self, master)
+        self._observer = observer
+        self._isTyping = False
+        self._createWidgets()
+
+    def _onSendMessage(self, event):
+        send_text = self._typingBox.get("1.0", tk.END).strip()
+        if send_text == '':
+            return
+
+        self.addMessage('me: ' + send_text)
+        self._typingBox.delete("0.0", tk.END)
+        self._onTyping(None)
+
+        # notify app for sending message
+        self._observer.onSendMessage(send_text)
+
+    def _onTyping(self, event):
+        # notify app for typing indication
+        is_typing = self._typingBox.get("1.0", tk.END).strip() != ''
+        if is_typing != self._isTyping:
+            self._isTyping = is_typing
+            if is_typing:
+                self._observer.onStartTyping()
+            else:
+                self._observer.onStopTyping()
+
+    def _createWidgets(self):
+        self.rowconfigure(0, weight=1)
+        self.rowconfigure(1, weight=0)
+        self.rowconfigure(2, weight=0)
+        self.columnconfigure(0, weight=1)
+        self.columnconfigure(1, weight=0)
+
+        self._text = tk.Text(self, width=50, height=30, font=("Arial", "10"))
+        self._text.grid(row=0, column=0, sticky='nswe')
+        self._text.config(state=tk.DISABLED)
+        self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic"))
+
+        scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview)
+        self._text.config(yscrollcommand=scrl.set)
+        scrl.grid(row=0, column=1, sticky='nsw')
+
+        self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10"))
+        self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0)
+
+        self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic"))
+        self._statusBar.grid(row=2, columnspan=2, sticky='we')
+
+        self._typingBox.bind('<Return>', self._onSendMessage)
+        self._typingBox.bind("<Key>", self._onTyping)
+        self._typingBox.focus_set()
+
+    def addMessage(self, msg, is_chat = True):
+        self._text.config(state=tk.NORMAL)
+        if is_chat:
+            self._text.insert(tk.END, msg+'\r\n')
+        else:
+            self._text.insert(tk.END, msg+'\r\n', 'info')
+        self._text.config(state=tk.DISABLED)
+        self._text.yview(tk.END)
+
+    def setTypingIndication(self, who, is_typing):
+        if is_typing:
+            self._statusBar['text'] = "'%s' is typing.." % (who)
+        else:
+            self._statusBar['text'] = ''
 
 class AudioState:
-	NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5)
-			
+    NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5)
+
 class AudioObserver:
-	def onHangup(self, peer_uri):
-		pass
-	def onHold(self, peer_uri):
-		pass
-	def onUnhold(self, peer_uri):
-		pass
-	def onRxMute(self, peer_uri, is_muted):
-		pass
-	def onRxVol(self, peer_uri, vol_pct):
-		pass
-	def onTxMute(self, peer_uri, is_muted):
-		pass
-			
+    def onHangup(self, peer_uri):
+        pass
+    def onHold(self, peer_uri):
+        pass
+    def onUnhold(self, peer_uri):
+        pass
+    def onRxMute(self, peer_uri, is_muted):
+        pass
+    def onRxVol(self, peer_uri, vol_pct):
+        pass
+    def onTxMute(self, peer_uri, is_muted):
+        pass
+
 
 class AudioFrame(ttk.Labelframe):
-	def __init__(self, master, peer_uri, observer):
-		ttk.Labelframe.__init__(self, master, text=peer_uri)
-		self.peerUri = peer_uri
-		self._observer = observer
-		self._initFrame = None
-		self._callFrame = None
-		self._rxMute = False
-		self._txMute = False
-		self._state = AudioState.NULL
-		
-		self._createInitWidgets()
-		self._createWidgets()
-		
-	def updateState(self, state):
-		if self._state == state:
-			return
-
-		if state == AudioState.INITIALIZING:
-			self._callFrame.pack_forget()
-			self._initFrame.pack(fill=tk.BOTH)
-			self._btnCancel.pack(side=tk.TOP)
-			self._lblInitState['text'] = 'Intializing..'
-
-		elif state == AudioState.CONNECTED:
-			self._initFrame.pack_forget()
-			self._callFrame.pack(fill=tk.BOTH)			
-		else:
-			self._callFrame.pack_forget()
-			self._initFrame.pack(fill=tk.BOTH)
-			if state == AudioState.FAILED:
-				self._lblInitState['text'] = 'Failed'
-			else:
-				self._lblInitState['text'] = 'Normal cleared'
-				self._btnCancel.pack_forget()
-			
-			self._btnHold['text'] = 'Hold'
-			self._btnHold.config(state=tk.NORMAL)
-			self._rxMute = False
-			self._txMute = False
-			self.btnRxMute['text'] = 'Mute'
-			self.btnTxMute['text'] = 'Mute'
-			self.rxVol.set(5.0)
-		
-		# save last state
-		self._state = state
-		
-	def setStatsText(self, stats_str):
-		self.stat.config(state=tk.NORMAL)
-		self.stat.delete("0.0", tk.END)
-		self.stat.insert(tk.END, stats_str)
-		self.stat.config(state=tk.DISABLED)
-		
-	def _onHold(self):
-		self._btnHold.config(state=tk.DISABLED)
-		# notify app
-		if self._btnHold['text'] == 'Hold':
-			self._observer.onHold(self.peerUri)
-			self._btnHold['text'] = 'Unhold'
-		else:
-			self._observer.onUnhold(self.peerUri)
-			self._btnHold['text'] = 'Hold'
-		self._btnHold.config(state=tk.NORMAL)
-
-	def _onHangup(self):
-		# notify app
-		self._observer.onHangup(self.peerUri)
-
-	def _onRxMute(self):
-		# notify app
-		self._rxMute = not self._rxMute
-		self._observer.onRxMute(self.peerUri, self._rxMute)
-		self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute'
-		
-	def _onRxVol(self, event):
-		# notify app
-		vol = self.rxVol.get()
-		self._observer.onRxVol(self.peerUri, vol*10.0)
-
-	def _onTxMute(self):
-		# notify app
-		self._txMute = not self._txMute
-		self._observer.onTxMute(self.peerUri, self._txMute)
-		self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute'
-
-	def _createInitWidgets(self):
-		self._initFrame = ttk.Frame(self)
-		#self._initFrame.pack(fill=tk.BOTH)
-
-	
-		self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='')
-		self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1)
-		
-		# Operation: cancel/kick
-		self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup)
-		self._btnCancel.pack(side=tk.TOP)
-				
-	def _createWidgets(self):
-		self._callFrame = ttk.Frame(self)
-		#self._callFrame.pack(fill=tk.BOTH)
-		
-		# toolbar
-		toolbar = ttk.Frame(self._callFrame)
-		toolbar.pack(side=tk.TOP, fill=tk.X)
-		self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold)
-		self._btnHold.pack(side=tk.LEFT, fill=tk.Y)
-		#self._btnXfer = ttk.Button(toolbar, text='Transfer..')
-		#self._btnXfer.pack(side=tk.LEFT, fill=tk.Y)
-		self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup)
-		self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y)
-
-		# volume tool
-		vol_frm = ttk.Frame(self._callFrame)
-		vol_frm.pack(side=tk.TOP, fill=tk.X)
-		
-		self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume')
-		self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y)
-		
-		self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute)
-		self.btnRxMute.pack(side=tk.LEFT)
-		self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1)
-		self.rxVol.set(5.0)
-		self.rxVol.bind("<ButtonRelease-1>", self._onRxVol)
-		self.rxVol.pack(side=tk.LEFT)
-		
-		self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume')
-		self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y)
-		
-		self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute)
-		self.btnTxMute.pack(side=tk.LEFT)
-		
-		# stat
-		self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Courier", "9"))
-		self.stat.insert(tk.END, 'stat here')
-		self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
+    def __init__(self, master, peer_uri, observer):
+        ttk.Labelframe.__init__(self, master, text=peer_uri)
+        self.peerUri = peer_uri
+        self._observer = observer
+        self._initFrame = None
+        self._callFrame = None
+        self._rxMute = False
+        self._txMute = False
+        self._state = AudioState.NULL
+
+        self._createInitWidgets()
+        self._createWidgets()
+
+    def updateState(self, state):
+        if self._state == state:
+            return
+
+        if state == AudioState.INITIALIZING:
+            self._callFrame.pack_forget()
+            self._initFrame.pack(fill=tk.BOTH)
+            self._btnCancel.pack(side=tk.TOP)
+            self._lblInitState['text'] = 'Intializing..'
+
+        elif state == AudioState.CONNECTED:
+            self._initFrame.pack_forget()
+            self._callFrame.pack(fill=tk.BOTH)
+        else:
+            self._callFrame.pack_forget()
+            self._initFrame.pack(fill=tk.BOTH)
+            if state == AudioState.FAILED:
+                self._lblInitState['text'] = 'Failed'
+            else:
+                self._lblInitState['text'] = 'Normal cleared'
+                self._btnCancel.pack_forget()
+
+            self._btnHold['text'] = 'Hold'
+            self._btnHold.config(state=tk.NORMAL)
+            self._rxMute = False
+            self._txMute = False
+            self.btnRxMute['text'] = 'Mute'
+            self.btnTxMute['text'] = 'Mute'
+            self.rxVol.set(5.0)
+
+        # save last state
+        self._state = state
+
+    def setStatsText(self, stats_str):
+        self.stat.config(state=tk.NORMAL)
+        self.stat.delete("0.0", tk.END)
+        self.stat.insert(tk.END, stats_str)
+        self.stat.config(state=tk.DISABLED)
+
+    def _onHold(self):
+        self._btnHold.config(state=tk.DISABLED)
+        # notify app
+        if self._btnHold['text'] == 'Hold':
+            self._observer.onHold(self.peerUri)
+            self._btnHold['text'] = 'Unhold'
+        else:
+            self._observer.onUnhold(self.peerUri)
+            self._btnHold['text'] = 'Hold'
+        self._btnHold.config(state=tk.NORMAL)
+
+    def _onHangup(self):
+        # notify app
+        self._observer.onHangup(self.peerUri)
+
+    def _onRxMute(self):
+        # notify app
+        self._rxMute = not self._rxMute
+        self._observer.onRxMute(self.peerUri, self._rxMute)
+        self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute'
+
+    def _onRxVol(self, event):
+        # notify app
+        vol = self.rxVol.get()
+        self._observer.onRxVol(self.peerUri, vol*10.0)
+
+    def _onTxMute(self):
+        # notify app
+        self._txMute = not self._txMute
+        self._observer.onTxMute(self.peerUri, self._txMute)
+        self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute'
+
+    def _createInitWidgets(self):
+        self._initFrame = ttk.Frame(self)
+        #self._initFrame.pack(fill=tk.BOTH)
+
+
+        self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='')
+        self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1)
+
+        # Operation: cancel/kick
+        self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup)
+        self._btnCancel.pack(side=tk.TOP)
+
+    def _createWidgets(self):
+        self._callFrame = ttk.Frame(self)
+        #self._callFrame.pack(fill=tk.BOTH)
+
+        # toolbar
+        toolbar = ttk.Frame(self._callFrame)
+        toolbar.pack(side=tk.TOP, fill=tk.X)
+        self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold)
+        self._btnHold.pack(side=tk.LEFT, fill=tk.Y)
+        #self._btnXfer = ttk.Button(toolbar, text='Transfer..')
+        #self._btnXfer.pack(side=tk.LEFT, fill=tk.Y)
+        self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup)
+        self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y)
+
+        # volume tool
+        vol_frm = ttk.Frame(self._callFrame)
+        vol_frm.pack(side=tk.TOP, fill=tk.X)
+
+        self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume')
+        self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y)
+
+        self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute)
+        self.btnRxMute.pack(side=tk.LEFT)
+        self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1)
+        self.rxVol.set(5.0)
+        self.rxVol.bind("<ButtonRelease-1>", self._onRxVol)
+        self.rxVol.pack(side=tk.LEFT)
+
+        self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume')
+        self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y)
+
+        self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute)
+        self.btnTxMute.pack(side=tk.LEFT)
+
+        # stat
+        self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Courier", "9"))
+        self.stat.insert(tk.END, 'stat here')
+        self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 
 
 class ChatObserver(TextObserver, AudioObserver):
-	def onAddParticipant(self):
-		pass
-	def onStartAudio(self):
-		pass
-	def onStopAudio(self):
-		pass
-	def onCloseWindow(self):
-		pass
-		
+    def onAddParticipant(self):
+        pass
+    def onStartAudio(self):
+        pass
+    def onStopAudio(self):
+        pass
+    def onCloseWindow(self):
+        pass
+
 class ChatFrame(tk.Toplevel):
-	"""
-	Room
-	"""
-	def __init__(self, observer):
-		tk.Toplevel.__init__(self)
-		self.protocol("WM_DELETE_WINDOW", self._onClose)
-		self._observer = observer
-
-		self._text = None
-		self._text_shown = True
-		
-		self._audioEnabled = False
-		self._audioFrames = []
-		self._createWidgets()
-	
-	def _createWidgets(self):
-		# toolbar
-		self.toolbar = ttk.Frame(self)
-		self.toolbar.pack(side=tk.TOP, fill=tk.BOTH)
-		
-		btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText)
-		btnText.pack(side=tk.LEFT, fill=tk.Y)
-		btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio)
-		btnAudio.pack(side=tk.LEFT, fill=tk.Y)
-		
-		ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4)
-
-		btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant)
-		btnAdd.pack(side=tk.LEFT, fill=tk.Y)
-
-		# media frame
-		self.media = ttk.Frame(self)
-		self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
-		
-		# create Text Chat frame
-		self.media_left = ttk.Frame(self.media)
-		self._text = TextFrame(self.media_left, self._observer)
-		self._text.pack(fill=tk.BOTH, expand=1)
-		self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
-		
-		# create other media frame
-		self.media_right = ttk.Frame(self.media)
-		
-	def _arrangeMediaFrames(self):
-		if len(self._audioFrames) == 0:
-			self.media_right.pack_forget()
-			return
-		
-		self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
-		MAX_ROWS = 3
-		row_num = 0
-		col_num = 1
-		for frm in self._audioFrames:
-			frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5)
-			row_num += 1
-			if row_num >= MAX_ROWS:
-				row_num  = 0
-				col_num += 1
-	
-	def _onShowHideText(self):
-		self.textShowHide(not self._text_shown)
-		
-	def _onAddParticipant(self):
-		self._observer.onAddParticipant()
-	
-	def _onStartStopAudio(self):
-		self._audioEnabled = not self._audioEnabled
-		if self._audioEnabled:
-			self._observer.onStartAudio()
-		else:
-			self._observer.onStopAudio()
-		self.enableAudio(self._audioEnabled)
-		
-	def _onClose(self):
-		self._observer.onCloseWindow()
-			
-	# APIs
-	
-	def bringToFront(self):
-		self.deiconify()
-		self.lift()
-		self._text._typingBox.focus_set()
-		
-	def textAddMessage(self, msg, is_chat = True):
-		self._text.addMessage(msg, is_chat)
-		
-	def textSetTypingIndication(self, who, is_typing = True):
-		self._text.setTypingIndication(who, is_typing)
-		
-	def addParticipant(self, participant_uri):
-		aud_frm = AudioFrame(self.media_right, participant_uri, self._observer)
-		self._audioFrames.append(aud_frm)
-	
-	def delParticipant(self, participant_uri):
-		for aud_frm in self._audioFrames:
-			if participant_uri == aud_frm.peerUri:
-				self._audioFrames.remove(aud_frm)
-				# need to delete aud_frm manually?
-				aud_frm.destroy()
-				return
-
-	def textShowHide(self, show = True):
-		if show:
-			self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
-			self._text._typingBox.focus_set()
-		else:
-			self.media_left.pack_forget()
-		self._text_shown = show
-	
-	def enableAudio(self, is_enabled = True):
-		if is_enabled:
-			self._arrangeMediaFrames()
-		else:
-			self.media_right.pack_forget()
-		self._audioEnabled = is_enabled
-			
-	def audioUpdateState(self, participant_uri, state):
-		for aud_frm in self._audioFrames:
-			if participant_uri == aud_frm.peerUri:
-				aud_frm.updateState(state)
-				break
-		if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1:
-			self.enableAudio(False)
-		else:
-			self.enableAudio(True)
-			
-	def audioSetStatsText(self, participant_uri, stats_str):
-		for aud_frm in self._audioFrames:
-			if participant_uri == aud_frm.peerUri:
-				aud_frm.setStatsText(stats_str)
-				break
-				
+    """
+    Room
+    """
+    def __init__(self, observer):
+        tk.Toplevel.__init__(self)
+        self.protocol("WM_DELETE_WINDOW", self._onClose)
+        self._observer = observer
+
+        self._text = None
+        self._text_shown = True
+
+        self._audioEnabled = False
+        self._audioFrames = []
+        self._createWidgets()
+
+    def _createWidgets(self):
+        # toolbar
+        self.toolbar = ttk.Frame(self)
+        self.toolbar.pack(side=tk.TOP, fill=tk.BOTH)
+
+        btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText)
+        btnText.pack(side=tk.LEFT, fill=tk.Y)
+        btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio)
+        btnAudio.pack(side=tk.LEFT, fill=tk.Y)
+
+        ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4)
+
+        btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant)
+        btnAdd.pack(side=tk.LEFT, fill=tk.Y)
+
+        # media frame
+        self.media = ttk.Frame(self)
+        self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
+
+        # create Text Chat frame
+        self.media_left = ttk.Frame(self.media)
+        self._text = TextFrame(self.media_left, self._observer)
+        self._text.pack(fill=tk.BOTH, expand=1)
+        self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
+
+        # create other media frame
+        self.media_right = ttk.Frame(self.media)
+
+    def _arrangeMediaFrames(self):
+        if len(self._audioFrames) == 0:
+            self.media_right.pack_forget()
+            return
+
+        self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
+        MAX_ROWS = 3
+        row_num = 0
+        col_num = 1
+        for frm in self._audioFrames:
+            frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5)
+            row_num += 1
+            if row_num >= MAX_ROWS:
+                row_num  = 0
+                col_num += 1
+
+    def _onShowHideText(self):
+        self.textShowHide(not self._text_shown)
+
+    def _onAddParticipant(self):
+        self._observer.onAddParticipant()
+
+    def _onStartStopAudio(self):
+        self._audioEnabled = not self._audioEnabled
+        if self._audioEnabled:
+            self._observer.onStartAudio()
+        else:
+            self._observer.onStopAudio()
+        self.enableAudio(self._audioEnabled)
+
+    def _onClose(self):
+        self._observer.onCloseWindow()
+
+    # APIs
+
+    def bringToFront(self):
+        self.deiconify()
+        self.lift()
+        self._text._typingBox.focus_set()
+
+    def textAddMessage(self, msg, is_chat = True):
+        self._text.addMessage(msg, is_chat)
+
+    def textSetTypingIndication(self, who, is_typing = True):
+        self._text.setTypingIndication(who, is_typing)
+
+    def addParticipant(self, participant_uri):
+        aud_frm = AudioFrame(self.media_right, participant_uri, self._observer)
+        self._audioFrames.append(aud_frm)
+
+    def delParticipant(self, participant_uri):
+        for aud_frm in self._audioFrames:
+            if participant_uri == aud_frm.peerUri:
+                self._audioFrames.remove(aud_frm)
+                # need to delete aud_frm manually?
+                aud_frm.destroy()
+                return
+
+    def textShowHide(self, show = True):
+        if show:
+            self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
+            self._text._typingBox.focus_set()
+        else:
+            self.media_left.pack_forget()
+        self._text_shown = show
+
+    def enableAudio(self, is_enabled = True):
+        if is_enabled:
+            self._arrangeMediaFrames()
+        else:
+            self.media_right.pack_forget()
+        self._audioEnabled = is_enabled
+
+    def audioUpdateState(self, participant_uri, state):
+        for aud_frm in self._audioFrames:
+            if participant_uri == aud_frm.peerUri:
+                aud_frm.updateState(state)
+                break
+        if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1:
+            self.enableAudio(False)
+        else:
+            self.enableAudio(True)
+
+    def audioSetStatsText(self, participant_uri, stats_str):
+        for aud_frm in self._audioFrames:
+            if participant_uri == aud_frm.peerUri:
+                aud_frm.setStatsText(stats_str)
+                break
+
 if __name__ == '__main__':
-	root = tk.Tk()
-	root.title("Chat")
-	root.columnconfigure(0, weight=1)
-	root.rowconfigure(0, weight=1)
-	
-	obs = ChatObserver()
-	dlg = ChatFrame(obs)
-	#dlg = TextFrame(root)
-	#dlg = AudioFrame(root)
-
-	#dlg.pack(fill=tk.BOTH, expand=1)
-	root.mainloop()
+    root = tk.Tk()
+    root.title("Chat")
+    root.columnconfigure(0, weight=1)
+    root.rowconfigure(0, weight=1)
+
+    obs = ChatObserver()
+    dlg = ChatFrame(obs)
+    #dlg = TextFrame(root)
+    #dlg = AudioFrame(root)
+
+    #dlg.pack(fill=tk.BOTH, expand=1)
+    root.mainloop()
Index: /pjproject/trunk/pjsip-apps/src/pygui/endpoint.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/endpoint.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/endpoint.py	(revision 5638)
@@ -21,11 +21,11 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import pjsua2 as pj
@@ -34,20 +34,20 @@
 
 class Endpoint(pj.Endpoint):
-	"""
-	This is high level Python object inherited from pj.Endpoint
-	"""
-	instance = None
-	def __init__(self):
-		pj.Endpoint.__init__(self)
-		Endpoint.instance = self
-	
-	
+    """
+    This is high level Python object inherited from pj.Endpoint
+    """
+    instance = None
+    def __init__(self):
+        pj.Endpoint.__init__(self)
+        Endpoint.instance = self
+
+
 def validateUri(uri):
-	return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS
+    return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS
 
 def validateSipUri(uri):
-	return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS
+    return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS
 
 
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/pygui/log.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pygui/log.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/pygui/log.py	(revision 5638)
@@ -21,107 +21,108 @@
 import sys
 if sys.version_info[0] >= 3: # Python 3
-	import tkinter as tk
-	from tkinter import ttk
-	from tkinter import messagebox as msgbox
+    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 Tkinter as tk
+    import tkMessageBox as msgbox
+    import ttk
 
 import pjsua2 as pj
 import application
 
+write=sys.stdout.write
 
 class LogWindow(tk.Toplevel):
-	"""
-	Log window
-	"""
-	instance = None
-	def __init__(self, app):
-		tk.Toplevel.__init__(self, name='logwnd', width=640, height=480)
-		LogWindow.instance = self
-		self.app = app
-		self.state('withdrawn')
-		self.title('Log')
-		self._createWidgets()
-		self.protocol("WM_DELETE_WINDOW", self._onHide)
+    """
+    Log window
+    """
+    instance = None
+    def __init__(self, app):
+        tk.Toplevel.__init__(self, name='logwnd', width=640, height=480)
+        LogWindow.instance = self
+        self.app = app
+        self.state('withdrawn')
+        self.title('Log')
+        self._createWidgets()
+        self.protocol("WM_DELETE_WINDOW", self._onHide)
 
-	def addLog(self, entry):
-		"""entry fields:
-		    int		level;
-		    string	msg;
-		    long	threadId;
-		    string	threadName;
-		"""
-		self.addLog2(entry.level, entry.msg)
-		
-	def addLog2(self, level, msg):
-		if level==5:
-			tags = ('trace',)
-		elif level==3:
-			tags = ('info',)
-		elif level==2:
-			tags = ('warning',)
-		elif level<=1:
-			tags = ('error',)
-		else:
-			tags = None
-		self.text.insert(tk.END, msg, tags)
-		self.text.see(tk.END)
-		
-	def _createWidgets(self):
-		self.rowconfigure(0, weight=1)
-		self.rowconfigure(1, weight=0)
-		self.columnconfigure(0, weight=1)
-		self.columnconfigure(1, weight=0)
-		
-		self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5)
-		self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5)
-		
-		scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview)
-		self.text.config(yscrollcommand=scrl.set)
-		scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5)
+    def addLog(self, entry):
+        """entry fields:
+            int		level;
+            string	msg;
+            long	threadId;
+            string	threadName;
+        """
+        self.addLog2(entry.level, entry.msg)
 
-		scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview)
-		self.text.config(xscrollcommand=scrl.set)
-		scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5)
-		
-		self.text.bind("<Key>", self._onKey)
-		
-		self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black')
-		self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777')
-		self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black')
-		self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan')
-		self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red')
-	
-	def _onKey(self, event):
-		# Ignore key event to make text widget read-only
-		return "break"
-	
-	def _onHide(self):
-		# Hide when close ('x') button is clicked
-		self.withdraw()
-		self.app.showLogWindow.set(0)
-		
-	
+    def addLog2(self, level, msg):
+        if level==5:
+            tags = ('trace',)
+        elif level==3:
+            tags = ('info',)
+        elif level==2:
+            tags = ('warning',)
+        elif level<=1:
+            tags = ('error',)
+        else:
+            tags = None
+        self.text.insert(tk.END, msg, tags)
+        self.text.see(tk.END)
+
+    def _createWidgets(self):
+        self.rowconfigure(0, weight=1)
+        self.rowconfigure(1, weight=0)
+        self.columnconfigure(0, weight=1)
+        self.columnconfigure(1, weight=0)
+
+        self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5)
+        self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5)
+
+        scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview)
+        self.text.config(yscrollcommand=scrl.set)
+        scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5)
+
+        scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview)
+        self.text.config(xscrollcommand=scrl.set)
+        scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5)
+
+        self.text.bind("<Key>", self._onKey)
+
+        self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black')
+        self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777')
+        self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black')
+        self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan')
+        self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red')
+
+    def _onKey(self, event):
+        # Ignore key event to make text widget read-only
+        return "break"
+
+    def _onHide(self):
+        # Hide when close ('x') button is clicked
+        self.withdraw()
+        self.app.showLogWindow.set(0)
+
+
 def writeLog2(level, msg):
-	if LogWindow.instance:
-		LogWindow.instance.addLog2(level, msg)	
+    if LogWindow.instance:
+        LogWindow.instance.addLog2(level, msg)
 
 def writeLog(entry):
-	if LogWindow.instance:
-		LogWindow.instance.addLog(entry)
+    if LogWindow.instance:
+        LogWindow.instance.addLog(entry)
 
 class Logger(pj.LogWriter):
-	"""
-	Logger to receive log messages from pjsua2
-	"""
-	def __init__(self):
-		pj.LogWriter.__init__(self)
-		
-	def write(self, entry):
-		print entry.msg,
-		writeLog(entry)
+    """
+    Logger to receive log messages from pjsua2
+    """
+    def __init__(self):
+        pj.LogWriter.__init__(self)
+
+    def write(self, entry):
+        write(entry.msg + "\r\n")
+        writeLog(entry)
 
 if __name__ == '__main__':
-	application.main()
+    application.main()
Index: /pjproject/trunk/pjsip-apps/src/swig/python/Makefile
===================================================================
--- /pjproject/trunk/pjsip-apps/src/swig/python/Makefile	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/swig/python/Makefile	(revision 5638)
@@ -1,3 +1,13 @@
 PYTHON_SO=_pjsua2.so
+
+USE_PYTHON3=1
+
+ifeq ($(USE_PYTHON3),1)
+  PYTHON_EXE=python3
+  PYTHON_PKG_DIR=$(HOME)/.local/lib/python3.6/site-packages
+else
+  PYTHON_EXE=python
+  PYTHON_PKG_DIR=$(HOME)/.local/lib/python2.7/site-packages
+endif
 
 #PYTHON_SETUP_FLAGS = --inplace 
@@ -17,5 +27,5 @@
 
 $(PYTHON_SO): pjsua2_wrap.cpp setup.py $(GCC_EXE)
-	python setup.py build $(PYTHON_SETUP_FLAGS)
+	$(PYTHON_EXE) setup.py build $(PYTHON_SETUP_FLAGS)
 
 gcc.exe: cc_mingw.c
@@ -31,8 +41,8 @@
 
 install:
-	python setup.py install --user
+	$(PYTHON_EXE) setup.py install --user
 
 uninstall:
-	rm -f $(HOME)/.local/lib/python2.7/site-packages/pjsua2*
-	rm -f $(HOME)/.local/lib/python2.7/site-packages/_pjsua2*
+	rm -f $(PYTHON_PKG_DIR)/pjsua2*
+	rm -f $(PYTHON_PKG_DIR)/_pjsua2*
 
Index: /pjproject/trunk/pjsip-apps/src/swig/python/setup.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/swig/python/setup.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/swig/python/setup.py	(revision 5638)
@@ -30,33 +30,35 @@
 pj_version_rev=""
 pj_version_suffix=""
+write=sys.stdout.write
 f = open('../../../../version.mak', 'r')
 for line in f:
+    tokens=""
     if line.find("export PJ_VERSION_MAJOR") != -1:
-    	tokens=line.split("=")
-	if len(tokens)>1:
-		pj_version_major= tokens[1].strip()
+        tokens=line.split("=")
+    if len(tokens)>1:
+        pj_version_major= tokens[1].strip()
     elif line.find("export PJ_VERSION_MINOR") != -1:
-    	tokens=line.split("=")
-	if len(tokens)>1:
-		pj_version_minor= line.split("=")[1].strip()
+        tokens=line.split("=")
+    if len(tokens)>1:
+        pj_version_minor= line.split("=")[1].strip()
     elif line.find("export PJ_VERSION_REV") != -1:
-    	tokens=line.split("=")
-	if len(tokens)>1:
-		pj_version_rev= line.split("=")[1].strip()
+        tokens=line.split("=")
+    if len(tokens)>1:
+        pj_version_rev= line.split("=")[1].strip()
     elif line.find("export PJ_VERSION_SUFFIX") != -1:
-    	tokens=line.split("=")
-	if len(tokens)>1:
-		pj_version_suffix= line.split("=")[1].strip()
+        tokens=line.split("=")
+    if len(tokens)>1:
+        pj_version_suffix= line.split("=")[1].strip()
 
 f.close()
 if not pj_version_major:
-    print 'Unable to get PJ_VERSION_MAJOR'
+    write("Unable to get PJ_VERSION_MAJOR" + "\r\n")
     sys.exit(1)
 
 pj_version = pj_version_major + "." + pj_version_minor
 if pj_version_rev:
-	pj_version += "." + pj_version_rev
+    pj_version += "." + pj_version_rev
 if pj_version_suffix:
-	pj_version += "-" + pj_version_suffix
+    pj_version += "-" + pj_version_suffix
 
 #print 'PJ_VERSION = "'+ pj_version + '"'
Index: /pjproject/trunk/pjsip-apps/src/swig/python/test.py
===================================================================
--- /pjproject/trunk/pjsip-apps/src/swig/python/test.py	(revision 5637)
+++ /pjproject/trunk/pjsip-apps/src/swig/python/test.py	(revision 5638)
@@ -2,4 +2,6 @@
 import sys
 import time
+
+write=sys.stdout.write
 
 #
@@ -8,39 +10,39 @@
 #
 def ua_data_test():
-	#
-	# AuthCredInfo
-	#
-	print "UA data types test.."
-	the_realm = "pjsip.org"
-	ci = pj.AuthCredInfo()
-	ci.realm = the_realm
-	ci.dataType = 20
-	
-	ci2 = ci
-	assert ci.dataType == 20
-	assert ci2.realm == the_realm
-	
-	#
-	# UaConfig
-	# See here how we manipulate std::vector
-	#
-	uc = pj.UaConfig()
-	uc.maxCalls = 10
-	uc.userAgent = "Python"
-	uc.nameserver = pj.StringVector(["10.0.0.1", "10.0.0.2"])
-	uc.nameserver.append("NS1")
-	
-	uc2 = uc
-	assert uc2.maxCalls == 10
-	assert uc2.userAgent == "Python"
-	assert len(uc2.nameserver) == 3
-	assert uc2.nameserver[0] == "10.0.0.1"
-	assert uc2.nameserver[1] == "10.0.0.2"
-	assert uc2.nameserver[2] == "NS1"
+    #
+    # AuthCredInfo
+    #
+    write("UA data types test..")
+    the_realm = "pjsip.org"
+    ci = pj.AuthCredInfo()
+    ci.realm = the_realm
+    ci.dataType = 20
 
-	print "  Dumping nameservers: ",
-	for s in uc2.nameserver:
-		print s,
-	print ""
+    ci2 = ci
+    assert ci.dataType == 20
+    assert ci2.realm == the_realm
+
+    #
+    # UaConfig
+    # See here how we manipulate std::vector
+    #
+    uc = pj.UaConfig()
+    uc.maxCalls = 10
+    uc.userAgent = "Python"
+    uc.nameserver = pj.StringVector(["10.0.0.1", "10.0.0.2"])
+    uc.nameserver.append("NS1")
+
+    uc2 = uc
+    assert uc2.maxCalls == 10
+    assert uc2.userAgent == "Python"
+    assert len(uc2.nameserver) == 3
+    assert uc2.nameserver[0] == "10.0.0.1"
+    assert uc2.nameserver[1] == "10.0.0.2"
+    assert uc2.nameserver[2] == "NS1"
+
+    write("  Dumping nameservers: " + "\r\n")
+    for s in uc2.nameserver:
+        write(s  + "\r\n")
+    write("\r\n")
 
 #
@@ -48,18 +50,19 @@
 #
 def ua_run_test_exception():
-	print "Exception test.."
-	ep = pj.Endpoint()
-	ep.libCreate()
-	got_exception = False
-	try:
-		ep.natDetectType()
-	except pj.Error, e:
-		got_exception = True
-		print "  Got exception: status=%u, reason=%s,\n  title=%s,\n  srcFile=%s, srcLine=%d" % \
-			(e.status, e.reason, e.title, e.srcFile, e.srcLine)
-		assert e.status == 370050
-		assert e.reason.find("PJNATH_ESTUNINSERVER") >= 0
-		assert e.title == "pjsua_detect_nat_type()"
-	assert got_exception
+    write("Exception test.." + "\r\n")
+    ep = pj.Endpoint()
+    ep.libCreate()
+    got_exception = False
+    try:
+        ep.natDetectType()
+    except pj.Error as e:
+        #t, e = sys.exc_info()[:2]
+        got_exception = True
+        write("  Got exception: status=%u, reason=%s,\n  title=%s,\n  srcFile=%s, srcLine=%d" % \
+            (e.status, e.reason, e.title, e.srcFile, e.srcLine) + "\r\n")
+        assert e.status == 370050
+        assert e.reason.find("PJNATH_ESTUNINSERVER") >= 0
+        assert e.title == "pjsua_detect_nat_type()"
+    assert got_exception
 
 #
@@ -67,37 +70,37 @@
 #
 class MyLogWriter(pj.LogWriter):
-	def write(self, entry):
-		print "This is Python:", entry.msg
-		
+    def write(self, entry):
+        write("This is Python:" + entry.msg + "\r\n")
+
 #
 # Testing log writer callback
 #
 def ua_run_log_test():
-	print "Logging test.."
-	ep_cfg = pj.EpConfig()
-	
-	lw = MyLogWriter()
-	ep_cfg.logConfig.writer = lw
-	ep_cfg.logConfig.decor = ep_cfg.logConfig.decor & ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE) 
-	
-	ep = pj.Endpoint()
-	ep.libCreate()
-	ep.libInit(ep_cfg)
-	ep.libDestroy()
-	
+    write("Logging test.." + "\r\n")
+    ep_cfg = pj.EpConfig()
+
+    lw = MyLogWriter()
+    ep_cfg.logConfig.writer = lw
+    ep_cfg.logConfig.decor = ep_cfg.logConfig.decor & ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE)
+
+    ep = pj.Endpoint()
+    ep.libCreate()
+    ep.libInit(ep_cfg)
+    ep.libDestroy()
+
 #
 # Simple create, init, start, and destroy sequence
 #
 def ua_run_ua_test():
-	print "UA test run.."
-	ep_cfg = pj.EpConfig()
-	
-	ep = pj.Endpoint()
-	ep.libCreate()
-	ep.libInit(ep_cfg)
-	ep.libStart()
-	
-	print "************* Endpoint started ok, now shutting down... *************"
-	ep.libDestroy()
+    write("UA test run.." + "\r\n")
+    ep_cfg = pj.EpConfig()
+
+    ep = pj.Endpoint()
+    ep.libCreate()
+    ep.libInit(ep_cfg)
+    ep.libStart()
+
+    write("************* Endpoint started ok, now shutting down... *************" + "\r\n")
+    ep.libDestroy()
 
 #
@@ -105,54 +108,54 @@
 #
 def ua_tonegen_test():
-        print "UA tonegen test.."
-        ep_cfg = pj.EpConfig()
+    write("UA tonegen test.." + "\r\n")
+    ep_cfg = pj.EpConfig()
 
-        ep = pj.Endpoint()
-        ep.libCreate()
-        ep.libInit(ep_cfg)
-        ep.libStart()
-        
-        tonegen = pj.ToneGenerator()
-        tonegen.createToneGenerator()
+    ep = pj.Endpoint()
+    ep.libCreate()
+    ep.libInit(ep_cfg)
+    ep.libStart()
 
-        tone = pj.ToneDesc()
-        tone.freq1 = 400
-        tone.freq2 = 600
-        tone.on_msec = 1000
-        tone.off_msec = 1000
-        tones = pj.ToneDescVector()
-        tones.append(tone)
+    tonegen = pj.ToneGenerator()
+    tonegen.createToneGenerator()
 
-        digit = pj.ToneDigit()
-        digit.digit = '0'
-        digit.on_msec = 1000
-        digit.off_msec = 1000
-        digits = pj.ToneDigitVector()
-        digits.append(digit)
+    tone = pj.ToneDesc()
+    tone.freq1 = 400
+    tone.freq2 = 600
+    tone.on_msec = 1000
+    tone.off_msec = 1000
+    tones = pj.ToneDescVector()
+    tones.append(tone)
 
-        adm = ep.audDevManager()
-        spk = adm.getPlaybackDevMedia()
+    digit = pj.ToneDigit()
+    digit.digit = '0'
+    digit.on_msec = 1000
+    digit.off_msec = 1000
+    digits = pj.ToneDigitVector()
+    digits.append(digit)
 
-        tonegen.play(tones, True)
-        tonegen.startTransmit(spk)
-        time.sleep(5)
+    adm = ep.audDevManager()
+    spk = adm.getPlaybackDevMedia()
 
-	tonegen.stop()
-        tonegen.playDigits(digits, True)
-        time.sleep(5)
+    tonegen.play(tones, True)
+    tonegen.startTransmit(spk)
+    time.sleep(5)
 
-	dm = tonegen.getDigitMap()
-	print dm[0].digit
-	dm[0].freq1 = 400
-	dm[0].freq2 = 600
-	tonegen.setDigitMap(dm)
-	
-	tonegen.stop()
-        tonegen.playDigits(digits, True)
-        time.sleep(5)
-	
-        tonegen = None
+    tonegen.stop()
+    tonegen.playDigits(digits, True)
+    time.sleep(5)
 
-        ep.libDestroy()
+    dm = tonegen.getDigitMap()
+    write(dm[0].digit + "\r\n")
+    dm[0].freq1 = 400
+    dm[0].freq2 = 600
+    tonegen.setDigitMap(dm)
+
+    tonegen.stop()
+    tonegen.playDigits(digits, True)
+    time.sleep(5)
+
+    tonegen = None
+
+    ep.libDestroy()
 
 #
@@ -160,10 +163,10 @@
 #
 if __name__ == "__main__":
-	ua_data_test()
-	ua_run_test_exception()
-	ua_run_log_test()
-	ua_run_ua_test()
-	ua_tonegen_test()
-	sys.exit(0)
+    ua_data_test()
+    ua_run_test_exception()
+    ua_run_log_test()
+    ua_run_ua_test()
+    ua_tonegen_test()
+    sys.exit(0)
 
-	
+
