Changeset 5638 for pjproject/trunk/pjsip-apps/src/pygui/chatgui.py
- Timestamp:
- Aug 2, 2017 9:45:09 AM (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/trunk/pjsip-apps/src/pygui/chatgui.py
r4757 r5638 21 21 import sys 22 22 if sys.version_info[0] >= 3: # Python 3 23 24 25 23 import tkinter as tk 24 from tkinter import ttk 25 from tkinter import messagebox as msgbox 26 26 else: 27 28 29 27 import Tkinter as tk 28 import ttk 29 import tkMessageBox as msgbox 30 30 31 31 32 32 class TextObserver: 33 34 35 36 37 38 39 33 def onSendMessage(self, msg): 34 pass 35 def onStartTyping(self): 36 pass 37 def onStopTyping(self): 38 pass 39 40 40 class TextFrame(ttk.Frame): 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 41 def __init__(self, master, observer): 42 ttk.Frame.__init__(self, master) 43 self._observer = observer 44 self._isTyping = False 45 self._createWidgets() 46 47 def _onSendMessage(self, event): 48 send_text = self._typingBox.get("1.0", tk.END).strip() 49 if send_text == '': 50 return 51 52 self.addMessage('me: ' + send_text) 53 self._typingBox.delete("0.0", tk.END) 54 self._onTyping(None) 55 56 # notify app for sending message 57 self._observer.onSendMessage(send_text) 58 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 64 if is_typing: 65 self._observer.onStartTyping() 66 else: 67 self._observer.onStopTyping() 68 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) 75 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")) 80 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') 84 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) 87 88 self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic")) 89 self._statusBar.grid(row=2, columnspan=2, sticky='we') 90 91 self._typingBox.bind('<Return>', self._onSendMessage) 92 self._typingBox.bind("<Key>", self._onTyping) 93 self._typingBox.focus_set() 94 95 def addMessage(self, msg, is_chat = True): 96 self._text.config(state=tk.NORMAL) 97 if is_chat: 98 self._text.insert(tk.END, msg+'\r\n') 99 else: 100 self._text.insert(tk.END, msg+'\r\n', 'info') 101 self._text.config(state=tk.DISABLED) 102 self._text.yview(tk.END) 103 104 def setTypingIndication(self, who, is_typing): 105 if is_typing: 106 self._statusBar['text'] = "'%s' is typing.." % (who) 107 else: 108 self._statusBar['text'] = '' 109 109 110 110 class AudioState: 111 112 111 NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5) 112 113 113 class AudioObserver: 114 115 116 117 118 119 120 121 122 123 124 125 126 114 def onHangup(self, peer_uri): 115 pass 116 def onHold(self, peer_uri): 117 pass 118 def onUnhold(self, peer_uri): 119 pass 120 def onRxMute(self, peer_uri, is_muted): 121 pass 122 def onRxVol(self, peer_uri, vol_pct): 123 pass 124 def onTxMute(self, peer_uri, is_muted): 125 pass 126 127 127 128 128 class AudioFrame(ttk.Labelframe): 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 self._callFrame.pack(fill=tk.BOTH) 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 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 135 self._rxMute = False 136 self._txMute = False 137 self._state = AudioState.NULL 138 139 self._createInitWidgets() 140 self._createWidgets() 141 142 def updateState(self, state): 143 if self._state == state: 144 return 145 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..' 151 152 elif state == AudioState.CONNECTED: 153 self._initFrame.pack_forget() 154 self._callFrame.pack(fill=tk.BOTH) 155 else: 156 self._callFrame.pack_forget() 157 self._initFrame.pack(fill=tk.BOTH) 158 if state == AudioState.FAILED: 159 self._lblInitState['text'] = 'Failed' 160 else: 161 self._lblInitState['text'] = 'Normal cleared' 162 self._btnCancel.pack_forget() 163 164 self._btnHold['text'] = 'Hold' 165 self._btnHold.config(state=tk.NORMAL) 166 self._rxMute = False 167 self._txMute = False 168 self.btnRxMute['text'] = 'Mute' 169 self.btnTxMute['text'] = 'Mute' 170 self.rxVol.set(5.0) 171 172 # save last state 173 self._state = state 174 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) 180 181 def _onHold(self): 182 self._btnHold.config(state=tk.DISABLED) 183 # notify app 184 if self._btnHold['text'] == 'Hold': 185 self._observer.onHold(self.peerUri) 186 self._btnHold['text'] = 'Unhold' 187 else: 188 self._observer.onUnhold(self.peerUri) 189 self._btnHold['text'] = 'Hold' 190 self._btnHold.config(state=tk.NORMAL) 191 192 def _onHangup(self): 193 # notify app 194 self._observer.onHangup(self.peerUri) 195 196 def _onRxMute(self): 197 # notify app 198 self._rxMute = not self._rxMute 199 self._observer.onRxMute(self.peerUri, self._rxMute) 200 self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute' 201 202 def _onRxVol(self, event): 203 # notify app 204 vol = self.rxVol.get() 205 self._observer.onRxVol(self.peerUri, vol*10.0) 206 207 def _onTxMute(self): 208 # notify app 209 self._txMute = not self._txMute 210 self._observer.onTxMute(self.peerUri, self._txMute) 211 self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute' 212 213 def _createInitWidgets(self): 214 self._initFrame = ttk.Frame(self) 215 #self._initFrame.pack(fill=tk.BOTH) 216 217 218 self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='') 219 self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1) 220 221 # Operation: cancel/kick 222 self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup) 223 self._btnCancel.pack(side=tk.TOP) 224 225 def _createWidgets(self): 226 self._callFrame = ttk.Frame(self) 227 #self._callFrame.pack(fill=tk.BOTH) 228 229 # toolbar 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) 238 239 # volume tool 240 vol_frm = ttk.Frame(self._callFrame) 241 vol_frm.pack(side=tk.TOP, fill=tk.X) 242 243 self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume') 244 self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y) 245 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) 249 self.rxVol.set(5.0) 250 self.rxVol.bind("<ButtonRelease-1>", self._onRxVol) 251 self.rxVol.pack(side=tk.LEFT) 252 253 self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume') 254 self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y) 255 256 self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute) 257 self.btnTxMute.pack(side=tk.LEFT) 258 259 # stat 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) 263 263 264 264 265 265 class ChatObserver(TextObserver, AudioObserver): 266 267 268 269 270 271 272 273 274 266 def onAddParticipant(self): 267 pass 268 def onStartAudio(self): 269 pass 270 def onStopAudio(self): 271 pass 272 def onCloseWindow(self): 273 pass 274 275 275 class ChatFrame(tk.Toplevel): 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 276 """ 277 Room 278 """ 279 def __init__(self, observer): 280 tk.Toplevel.__init__(self) 281 self.protocol("WM_DELETE_WINDOW", self._onClose) 282 self._observer = observer 283 284 self._text = None 285 self._text_shown = True 286 287 self._audioEnabled = False 288 self._audioFrames = [] 289 self._createWidgets() 290 291 def _createWidgets(self): 292 # toolbar 293 self.toolbar = ttk.Frame(self) 294 self.toolbar.pack(side=tk.TOP, fill=tk.BOTH) 295 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) 300 301 ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4) 302 303 btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant) 304 btnAdd.pack(side=tk.LEFT, fill=tk.Y) 305 306 # media frame 307 self.media = ttk.Frame(self) 308 self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 309 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) 315 316 # create other media frame 317 self.media_right = ttk.Frame(self.media) 318 319 def _arrangeMediaFrames(self): 320 if len(self._audioFrames) == 0: 321 self.media_right.pack_forget() 322 return 323 324 self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) 325 MAX_ROWS = 3 326 row_num = 0 327 col_num = 1 328 for frm in self._audioFrames: 329 frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5) 330 row_num += 1 331 if row_num >= MAX_ROWS: 332 row_num = 0 333 col_num += 1 334 335 def _onShowHideText(self): 336 self.textShowHide(not self._text_shown) 337 338 def _onAddParticipant(self): 339 self._observer.onAddParticipant() 340 341 def _onStartStopAudio(self): 342 self._audioEnabled = not self._audioEnabled 343 if self._audioEnabled: 344 self._observer.onStartAudio() 345 else: 346 self._observer.onStopAudio() 347 self.enableAudio(self._audioEnabled) 348 349 def _onClose(self): 350 self._observer.onCloseWindow() 351 352 # APIs 353 354 def bringToFront(self): 355 self.deiconify() 356 self.lift() 357 self._text._typingBox.focus_set() 358 359 def textAddMessage(self, msg, is_chat = True): 360 self._text.addMessage(msg, is_chat) 361 362 def textSetTypingIndication(self, who, is_typing = True): 363 self._text.setTypingIndication(who, is_typing) 364 365 def addParticipant(self, participant_uri): 366 aud_frm = AudioFrame(self.media_right, participant_uri, self._observer) 367 self._audioFrames.append(aud_frm) 368 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? 374 aud_frm.destroy() 375 return 376 377 def textShowHide(self, show = True): 378 if show: 379 self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 380 self._text._typingBox.focus_set() 381 else: 382 self.media_left.pack_forget() 383 self._text_shown = show 384 385 def enableAudio(self, is_enabled = True): 386 if is_enabled: 387 self._arrangeMediaFrames() 388 else: 389 self.media_right.pack_forget() 390 self._audioEnabled = is_enabled 391 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) 396 break 397 if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1: 398 self.enableAudio(False) 399 else: 400 self.enableAudio(True) 401 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) 406 break 407 408 408 if __name__ == '__main__': 409 410 411 412 413 414 415 416 417 418 419 420 409 root = tk.Tk() 410 root.title("Chat") 411 root.columnconfigure(0, weight=1) 412 root.rowconfigure(0, weight=1) 413 414 obs = ChatObserver() 415 dlg = ChatFrame(obs) 416 #dlg = TextFrame(root) 417 #dlg = AudioFrame(root) 418 419 #dlg.pack(fill=tk.BOTH, expand=1) 420 root.mainloop()
Note: See TracChangeset
for help on using the changeset viewer.