123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- #
- # 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=("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
- 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()
|