1
# $Id: chatgui.py 4757 2014-02-21 07:53:31Z nanang $
3
# pjsua Python GUI Demo
5
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
if sys.version_info[0] >= 3: # Python 3
24
from tkinter import ttk
25
from tkinter import messagebox as msgbox
29
import tkMessageBox as msgbox
33
def onSendMessage(self, msg):
35
def onStartTyping(self):
37
def onStopTyping(self):
40
class TextFrame(ttk.Frame):
41
def __init__(self, master, observer):
42
ttk.Frame.__init__(self, master)
43
self._observer = observer
44
self._isTyping = False
47
def _onSendMessage(self, event):
48
send_text = self._typingBox.get("1.0", tk.END).strip()
52
self.addMessage('me: ' + send_text)
53
self._typingBox.delete("0.0", tk.END)
56
# notify app for sending message
57
self._observer.onSendMessage(send_text)
59
def _onTyping(self, event):
60
# notify app for typing indication
61
is_typing = self._typingBox.get("1.0", tk.END).strip() != ''
62
if is_typing != self._isTyping:
63
self._isTyping = is_typing
65
self._observer.onStartTyping()
67
self._observer.onStopTyping()
69
def _createWidgets(self):
70
self.rowconfigure(0, weight=1)
71
self.rowconfigure(1, weight=0)
72
self.rowconfigure(2, weight=0)
73
self.columnconfigure(0, weight=1)
74
self.columnconfigure(1, weight=0)
76
self._text = tk.Text(self, width=50, height=30, font=("Arial", "10"))
77
self._text.grid(row=0, column=0, sticky='nswe')
78
self._text.config(state=tk.DISABLED)
79
self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic"))
81
scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview)
82
self._text.config(yscrollcommand=scrl.set)
83
scrl.grid(row=0, column=1, sticky='nsw')
85
self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10"))
86
self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0)
88
self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic"))
89
self._statusBar.grid(row=2, columnspan=2, sticky='we')
91
self._typingBox.bind('<Return>', self._onSendMessage)
92
self._typingBox.bind("<Key>", self._onTyping)
93
self._typingBox.focus_set()
95
def addMessage(self, msg, is_chat = True):
96
self._text.config(state=tk.NORMAL)
98
self._text.insert(tk.END, msg+'\r\n')
100
self._text.insert(tk.END, msg+'\r\n', 'info')
101
self._text.config(state=tk.DISABLED)
102
self._text.yview(tk.END)
104
def setTypingIndication(self, who, is_typing):
106
self._statusBar['text'] = "'%s' is typing.." % (who)
108
self._statusBar['text'] = ''
111
NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5)
114
def onHangup(self, peer_uri):
116
def onHold(self, peer_uri):
118
def onUnhold(self, peer_uri):
120
def onRxMute(self, peer_uri, is_muted):
122
def onRxVol(self, peer_uri, vol_pct):
124
def onTxMute(self, peer_uri, is_muted):
128
class AudioFrame(ttk.Labelframe):
129
def __init__(self, master, peer_uri, observer):
130
ttk.Labelframe.__init__(self, master, text=peer_uri)
131
self.peerUri = peer_uri
132
self._observer = observer
133
self._initFrame = None
134
self._callFrame = None
137
self._state = AudioState.NULL
139
self._createInitWidgets()
140
self._createWidgets()
142
def updateState(self, state):
143
if self._state == state:
146
if state == AudioState.INITIALIZING:
147
self._callFrame.pack_forget()
148
self._initFrame.pack(fill=tk.BOTH)
149
self._btnCancel.pack(side=tk.TOP)
150
self._lblInitState['text'] = 'Intializing..'
152
elif state == AudioState.CONNECTED:
153
self._initFrame.pack_forget()
154
self._callFrame.pack(fill=tk.BOTH)
156
self._callFrame.pack_forget()
157
self._initFrame.pack(fill=tk.BOTH)
158
if state == AudioState.FAILED:
159
self._lblInitState['text'] = 'Failed'
161
self._lblInitState['text'] = 'Normal cleared'
162
self._btnCancel.pack_forget()
164
self._btnHold['text'] = 'Hold'
165
self._btnHold.config(state=tk.NORMAL)
168
self.btnRxMute['text'] = 'Mute'
169
self.btnTxMute['text'] = 'Mute'
175
def setStatsText(self, stats_str):
176
self.stat.config(state=tk.NORMAL)
177
self.stat.delete("0.0", tk.END)
178
self.stat.insert(tk.END, stats_str)
179
self.stat.config(state=tk.DISABLED)
182
self._btnHold.config(state=tk.DISABLED)
184
if self._btnHold['text'] == 'Hold':
185
self._observer.onHold(self.peerUri)
186
self._btnHold['text'] = 'Unhold'
188
self._observer.onUnhold(self.peerUri)
189
self._btnHold['text'] = 'Hold'
190
self._btnHold.config(state=tk.NORMAL)
194
self._observer.onHangup(self.peerUri)
198
self._rxMute = not self._rxMute
199
self._observer.onRxMute(self.peerUri, self._rxMute)
200
self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute'
202
def _onRxVol(self, event):
204
vol = self.rxVol.get()
205
self._observer.onRxVol(self.peerUri, vol*10.0)
209
self._txMute = not self._txMute
210
self._observer.onTxMute(self.peerUri, self._txMute)
211
self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute'
213
def _createInitWidgets(self):
214
self._initFrame = ttk.Frame(self)
215
#self._initFrame.pack(fill=tk.BOTH)
218
self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='')
219
self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1)
221
# Operation: cancel/kick
222
self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup)
223
self._btnCancel.pack(side=tk.TOP)
225
def _createWidgets(self):
226
self._callFrame = ttk.Frame(self)
227
#self._callFrame.pack(fill=tk.BOTH)
230
toolbar = ttk.Frame(self._callFrame)
231
toolbar.pack(side=tk.TOP, fill=tk.X)
232
self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold)
233
self._btnHold.pack(side=tk.LEFT, fill=tk.Y)
234
#self._btnXfer = ttk.Button(toolbar, text='Transfer..')
235
#self._btnXfer.pack(side=tk.LEFT, fill=tk.Y)
236
self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup)
237
self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y)
240
vol_frm = ttk.Frame(self._callFrame)
241
vol_frm.pack(side=tk.TOP, fill=tk.X)
243
self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume')
244
self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y)
246
self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute)
247
self.btnRxMute.pack(side=tk.LEFT)
248
self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1)
250
self.rxVol.bind("<ButtonRelease-1>", self._onRxVol)
251
self.rxVol.pack(side=tk.LEFT)
253
self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume')
254
self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y)
256
self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute)
257
self.btnTxMute.pack(side=tk.LEFT)
260
self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Courier", "9"))
261
self.stat.insert(tk.END, 'stat here')
262
self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
265
class ChatObserver(TextObserver, AudioObserver):
266
def onAddParticipant(self):
268
def onStartAudio(self):
270
def onStopAudio(self):
272
def onCloseWindow(self):
275
class ChatFrame(tk.Toplevel):
279
def __init__(self, observer):
280
tk.Toplevel.__init__(self)
281
self.protocol("WM_DELETE_WINDOW", self._onClose)
282
self._observer = observer
285
self._text_shown = True
287
self._audioEnabled = False
288
self._audioFrames = []
289
self._createWidgets()
291
def _createWidgets(self):
293
self.toolbar = ttk.Frame(self)
294
self.toolbar.pack(side=tk.TOP, fill=tk.BOTH)
296
btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText)
297
btnText.pack(side=tk.LEFT, fill=tk.Y)
298
btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio)
299
btnAudio.pack(side=tk.LEFT, fill=tk.Y)
301
ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4)
303
btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant)
304
btnAdd.pack(side=tk.LEFT, fill=tk.Y)
307
self.media = ttk.Frame(self)
308
self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
310
# create Text Chat frame
311
self.media_left = ttk.Frame(self.media)
312
self._text = TextFrame(self.media_left, self._observer)
313
self._text.pack(fill=tk.BOTH, expand=1)
314
self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
316
# create other media frame
317
self.media_right = ttk.Frame(self.media)
319
def _arrangeMediaFrames(self):
320
if len(self._audioFrames) == 0:
321
self.media_right.pack_forget()
324
self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
328
for frm in self._audioFrames:
329
frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5)
331
if row_num >= MAX_ROWS:
335
def _onShowHideText(self):
336
self.textShowHide(not self._text_shown)
338
def _onAddParticipant(self):
339
self._observer.onAddParticipant()
341
def _onStartStopAudio(self):
342
self._audioEnabled = not self._audioEnabled
343
if self._audioEnabled:
344
self._observer.onStartAudio()
346
self._observer.onStopAudio()
347
self.enableAudio(self._audioEnabled)
350
self._observer.onCloseWindow()
354
def bringToFront(self):
357
self._text._typingBox.focus_set()
359
def textAddMessage(self, msg, is_chat = True):
360
self._text.addMessage(msg, is_chat)
362
def textSetTypingIndication(self, who, is_typing = True):
363
self._text.setTypingIndication(who, is_typing)
365
def addParticipant(self, participant_uri):
366
aud_frm = AudioFrame(self.media_right, participant_uri, self._observer)
367
self._audioFrames.append(aud_frm)
369
def delParticipant(self, participant_uri):
370
for aud_frm in self._audioFrames:
371
if participant_uri == aud_frm.peerUri:
372
self._audioFrames.remove(aud_frm)
373
# need to delete aud_frm manually?
377
def textShowHide(self, show = True):
379
self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
380
self._text._typingBox.focus_set()
382
self.media_left.pack_forget()
383
self._text_shown = show
385
def enableAudio(self, is_enabled = True):
387
self._arrangeMediaFrames()
389
self.media_right.pack_forget()
390
self._audioEnabled = is_enabled
392
def audioUpdateState(self, participant_uri, state):
393
for aud_frm in self._audioFrames:
394
if participant_uri == aud_frm.peerUri:
395
aud_frm.updateState(state)
397
if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1:
398
self.enableAudio(False)
400
self.enableAudio(True)
402
def audioSetStatsText(self, participant_uri, stats_str):
403
for aud_frm in self._audioFrames:
404
if participant_uri == aud_frm.peerUri:
405
aud_frm.setStatsText(stats_str)
408
if __name__ == '__main__':
411
root.columnconfigure(0, weight=1)
412
root.rowconfigure(0, weight=1)
416
#dlg = TextFrame(root)
417
#dlg = AudioFrame(root)
419
#dlg.pack(fill=tk.BOTH, expand=1)