diff options
Diffstat (limited to 'pjsip-apps/src/pygui/chatgui.py')
-rw-r--r-- | pjsip-apps/src/pygui/chatgui.py | 420 |
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() |