summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/pygui/chatgui.py
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/pygui/chatgui.py')
-rw-r--r--pjsip-apps/src/pygui/chatgui.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/pjsip-apps/src/pygui/chatgui.py b/pjsip-apps/src/pygui/chatgui.py
new file mode 100644
index 00000000..bb58e286
--- /dev/null
+++ b/pjsip-apps/src/pygui/chatgui.py
@@ -0,0 +1,420 @@
+# $Id$
+#
+# pjsua Python GUI Demo
+#
+# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+import sys
+if sys.version_info[0] >= 3: # Python 3
+ import tkinter as tk
+ from tkinter import ttk
+ from tkinter import messagebox as msgbox
+else:
+ import Tkinter as tk
+ import ttk
+ import tkMessageBox as msgbox
+
+
+class TextObserver:
+ 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'] = ''
+
+class AudioState:
+ 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
+
+
+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=("Arial", "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
+
+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
+
+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()