Ignore:
Timestamp:
Aug 2, 2017 9:45:09 AM (5 years ago)
Author:
riza
Message:

Close #2034: Add support to Python3 using PJSUA2 API.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjsip-apps/src/pygui/application.py

    r4798 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import pjsua2 as pj 
     
    4545# http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2014-March/017223.html 
    4646USE_THREADS = False 
    47          
     47 
     48write=sys.stdout.write 
     49 
    4850class Application(ttk.Frame): 
    49         """ 
    50         The Application main frame. 
    51         """ 
    52         def __init__(self): 
    53                 global USE_THREADS 
    54                 ttk.Frame.__init__(self, name='application', width=300, height=500) 
    55                 self.pack(expand='yes', fill='both') 
    56                 self.master.title('pjsua2 Demo') 
    57                 self.master.geometry('500x500+100+100') 
    58                  
    59                 # Logger 
    60                 self.logger = log.Logger() 
    61                  
    62                 # Accounts 
    63                 self.accList = [] 
    64                  
    65                 # GUI variables 
    66                 self.showLogWindow = tk.IntVar(value=0) 
    67                 self.quitting = False  
    68                  
    69                 # Construct GUI 
    70                 self._createWidgets() 
    71                  
    72                 # Log window 
    73                 self.logWindow = log.LogWindow(self) 
    74                 self._onMenuShowHideLogWindow() 
    75                  
    76                 # Instantiate endpoint 
    77                 self.ep = endpoint.Endpoint() 
    78                 self.ep.libCreate() 
    79                  
    80                 # Default config 
    81                 self.appConfig = settings.AppConfig() 
    82                 if USE_THREADS: 
    83                         self.appConfig.epConfig.uaConfig.threadCnt = 1 
    84                         self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
    85                 else: 
    86                         self.appConfig.epConfig.uaConfig.threadCnt = 0 
    87                         self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    88                 self.appConfig.epConfig.logConfig.writer = self.logger 
    89                 self.appConfig.epConfig.logConfig.filename = "pygui.log" 
    90                 self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND 
    91                 self.appConfig.epConfig.logConfig.level = 5 
    92                 self.appConfig.epConfig.logConfig.consoleLevel = 5 
    93                  
    94         def saveConfig(self, filename='pygui.js'): 
    95                 # Save disabled accounts since they are not listed in self.accList 
    96                 disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled] 
    97                 self.appConfig.accounts = [] 
    98                  
    99                 # Get account configs from active accounts 
    100                 for acc in self.accList: 
    101                         acfg = settings.AccConfig() 
    102                         acfg.enabled = True 
    103                         acfg.config = acc.cfg 
    104                         for bud in acc.buddyList: 
    105                                 acfg.buddyConfigs.append(bud.cfg) 
    106                         self.appConfig.accounts.append(acfg) 
    107                  
    108                 # Put back disabled accounts 
    109                 self.appConfig.accounts.extend(disabled_accs) 
    110                 # Save 
    111                 self.appConfig.saveFile(filename) 
    112          
    113         def start(self, cfg_file='pygui.js'): 
    114                 global USE_THREADS 
    115                 # Load config 
    116                 if cfg_file and os.path.exists(cfg_file): 
    117                         self.appConfig.loadFile(cfg_file) 
    118  
    119                 if USE_THREADS: 
    120                         self.appConfig.epConfig.uaConfig.threadCnt = 1 
    121                         self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
    122                 else: 
    123                         self.appConfig.epConfig.uaConfig.threadCnt = 0 
    124                         self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    125                 self.appConfig.epConfig.uaConfig.threadCnt = 0 
    126                 self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    127                 self.appConfig.epConfig.logConfig.writer = self.logger 
    128                 self.appConfig.epConfig.logConfig.level = 5 
    129                 self.appConfig.epConfig.logConfig.consoleLevel = 5 
    130                                  
    131                 # Initialize library 
    132                 self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full; 
    133                 self.ep.libInit(self.appConfig.epConfig) 
    134                 self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) 
    135                  
    136                 # Create transports 
    137                 if self.appConfig.udp.enabled: 
    138                         self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config) 
    139                 if self.appConfig.tcp.enabled: 
    140                         self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config) 
    141                 if self.appConfig.tls.enabled: 
    142                         self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) 
    143                          
    144                 # Add accounts 
    145                 for cfg in self.appConfig.accounts: 
    146                         if cfg.enabled: 
    147                                 self._createAcc(cfg.config) 
    148                                 acc = self.accList[-1] 
    149                                 for buddy_cfg in cfg.buddyConfigs: 
    150                                         self._createBuddy(acc, buddy_cfg) 
    151                                  
    152                 # Start library 
    153                 self.ep.libStart() 
    154                  
    155                 # Start polling 
    156                 if not USE_THREADS: 
    157                         self._onTimer() 
    158  
    159         def updateAccount(self, acc): 
    160                 if acc.deleting: 
    161                         return  # ignore 
    162                 iid = str(acc.randId) 
    163                 text = acc.cfg.idUri 
    164                 status = acc.statusText() 
    165                  
    166                 values = (status,) 
    167                 if self.tv.exists(iid): 
    168                         self.tv.item(iid, text=text, values=values) 
    169                 else: 
    170                         self.tv.insert('', 'end',  iid, open=True, text=text, values=values) 
    171                  
    172         def updateBuddy(self, bud): 
    173                 iid = 'buddy' + str(bud.randId) 
    174                 text = bud.cfg.uri 
    175                 status = bud.statusText() 
    176                  
    177                 values = (status,) 
    178                 if self.tv.exists(iid): 
    179                         self.tv.item(iid, text=text, values=values) 
    180                 else: 
    181                         self.tv.insert(str(bud.account.randId), 'end',  iid, open=True, text=text, values=values) 
    182                  
    183         def _createAcc(self, acc_cfg): 
    184                 acc = account.Account(self) 
    185                 acc.cfg = acc_cfg 
    186                 self.accList.append(acc) 
    187                 self.updateAccount(acc) 
    188                 acc.create(acc.cfg) 
    189                 acc.cfgChanged = False 
    190                 self.updateAccount(acc) 
    191                                  
    192         def _createBuddy(self, acc, buddy_cfg): 
    193                 bud = buddy.Buddy(self) 
    194                 bud.cfg = buddy_cfg 
    195                 bud.account = acc 
    196                 bud.create(acc, bud.cfg) 
    197                 self.updateBuddy(bud) 
    198                 acc.buddyList.append(bud) 
    199  
    200         def _createWidgets(self): 
    201                 self._createAppMenu() 
    202                  
    203                 # Main pane, a Treeview 
    204                 self.tv = ttk.Treeview(self, columns=('Status'), show='tree') 
    205                 self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) 
    206  
    207                 self._createContextMenu() 
    208                  
    209                 # Handle close event 
    210                 self.master.protocol("WM_DELETE_WINDOW", self._onClose) 
    211          
    212         def _createAppMenu(self): 
    213                 # Main menu bar 
    214                 top = self.winfo_toplevel() 
    215                 self.menubar = tk.Menu() 
    216                 top.configure(menu=self.menubar) 
    217                  
    218                 # File menu 
    219                 file_menu = tk.Menu(self.menubar, tearoff=False) 
    220                 self.menubar.add_cascade(label="File", menu=file_menu) 
    221                 file_menu.add_command(label="Add account..", command=self._onMenuAddAccount) 
    222                 file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow) 
    223                 file_menu.add_separator() 
    224                 file_menu.add_command(label="Settings...", command=self._onMenuSettings) 
    225                 file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings) 
    226                 file_menu.add_separator() 
    227                 file_menu.add_command(label="Quit", command=self._onMenuQuit) 
    228  
    229                 # Window menu 
    230                 self.window_menu = tk.Menu(self.menubar, tearoff=False) 
    231                 self.menubar.add_cascade(label="Window", menu=self.window_menu) 
    232                  
    233                 # Help menu 
    234                 help_menu = tk.Menu(self.menubar, tearoff=False) 
    235                 self.menubar.add_cascade(label="Help", menu=help_menu) 
    236                 help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) 
    237          
    238         def _showChatWindow(self, chat_inst): 
    239                 chat_inst.showWindow() 
    240                  
    241         def updateWindowMenu(self): 
    242                 # Chat windows 
    243                 self.window_menu.delete(0, tk.END) 
    244                 for acc in self.accList: 
    245                         for c in acc.chatList: 
    246                                 cmd = lambda arg=c: self._showChatWindow(arg) 
    247                                 self.window_menu.add_command(label=c.title, command=cmd) 
    248                  
    249         def _createContextMenu(self): 
    250                 top = self.winfo_toplevel() 
    251  
    252                 # Create Account context menu 
    253                 self.accMenu = tk.Menu(top, tearoff=False) 
    254                 # Labels, must match with _onAccContextMenu() 
    255                 labels = ['Unregister', 'Reregister', 'Add buddy...', '-', 
    256                           'Online', 'Invisible', 'Away', 'Busy', '-', 
    257                           'Settings...', '-', 
    258                           'Delete...'] 
    259                 for label in labels: 
    260                         if label=='-': 
    261                                 self.accMenu.add_separator() 
    262                         else: 
    263                                 cmd = lambda arg=label: self._onAccContextMenu(arg) 
    264                                 self.accMenu.add_command(label=label, command=cmd) 
    265                  
    266                 # Create Buddy context menu 
    267                 # Labels, must match with _onBuddyContextMenu() 
    268                 self.buddyMenu = tk.Menu(top, tearoff=False) 
    269                 labels = ['Audio call', 'Send instant message', '-', 
    270                           'Subscribe', 'Unsubscribe', '-', 
    271                           'Settings...', '-', 
    272                           'Delete...'] 
    273                  
    274                 for label in labels: 
    275                         if label=='-': 
    276                                 self.buddyMenu.add_separator() 
    277                         else: 
    278                                 cmd = lambda arg=label: self._onBuddyContextMenu(arg) 
    279                                 self.buddyMenu.add_command(label=label, command=cmd) 
    280                  
    281                 if (top.tk.call('tk', 'windowingsystem')=='aqua'): 
    282                         self.tv.bind('<2>', self._onTvRightClick) 
    283                         self.tv.bind('<Control-1>', self._onTvRightClick) 
    284                 else: 
    285                         self.tv.bind('<3>', self._onTvRightClick) 
    286                 self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) 
    287  
    288         def _getSelectedAccount(self): 
    289                 items = self.tv.selection() 
    290                 if not items: 
    291                         return None 
    292                 try: 
    293                         iid = int(items[0]) 
    294                 except: 
    295                         return None 
    296                 accs = [acc for acc in self.accList if acc.randId==iid] 
    297                 if not accs: 
    298                         return None 
    299                 return accs[0] 
    300          
    301         def _getSelectedBuddy(self): 
    302                 items = self.tv.selection() 
    303                 if not items: 
    304                         return None 
    305                 try: 
    306                         iid = int(items[0][5:]) 
    307                         iid_parent = int(self.tv.parent(items[0])) 
    308                 except: 
    309                         return None 
    310                          
    311                 accs = [acc for acc in self.accList if acc.randId==iid_parent] 
    312                 if not accs: 
    313                         return None 
    314                          
    315                 buds = [b for b in accs[0].buddyList if b.randId==iid] 
    316                 if not buds: 
    317                         return None 
    318                          
    319                 return buds[0] 
    320          
    321         def _onTvRightClick(self, event): 
    322                 iid = self.tv.identify_row(event.y) 
    323                 #iid = self.tv.identify('item', event.x, event.y) 
    324                 if iid: 
    325                         self.tv.selection_set( (iid,) ) 
    326                         acc = self._getSelectedAccount() 
    327                         if acc: 
    328                                 self.accMenu.post(event.x_root, event.y_root) 
    329                         else: 
    330                                 # A buddy is selected 
    331                                 self.buddyMenu.post(event.x_root, event.y_root) 
    332          
    333         def _onTvDoubleClick(self, event): 
    334                 iid = self.tv.identify_row(event.y) 
    335                 if iid: 
    336                         self.tv.selection_set( (iid,) ) 
    337                         acc = self._getSelectedAccount() 
    338                         if acc: 
    339                                 self.cfgChanged = False 
    340                                 dlg = accountsetting.Dialog(self.master, acc.cfg) 
    341                                 if dlg.doModal(): 
    342                                         self.updateAccount(acc) 
    343                                         acc.modify(acc.cfg) 
    344                         else: 
    345                                 bud = self._getSelectedBuddy() 
    346                                 acc = bud.account 
    347                                 chat = acc.findChat(bud.cfg.uri) 
    348                                 if not chat: 
    349                                         chat = acc.newChat(bud.cfg.uri) 
    350                                 chat.showWindow() 
    351          
    352         def _onAccContextMenu(self, label): 
    353                 acc = self._getSelectedAccount() 
    354                 if not acc: 
    355                         return 
    356                  
    357                 if label=='Unregister': 
    358                         acc.setRegistration(False) 
    359                 elif label=='Reregister': 
    360                         acc.setRegistration(True) 
    361                 elif label=='Online': 
    362                         ps = pj.PresenceStatus() 
    363                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    364                         acc.setOnlineStatus(ps) 
    365                 elif label=='Invisible': 
    366                         ps = pj.PresenceStatus() 
    367                         ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE 
    368                         acc.setOnlineStatus(ps) 
    369                 elif label=='Away': 
    370                         ps = pj.PresenceStatus() 
    371                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    372                         ps.activity = pj.PJRPID_ACTIVITY_AWAY 
    373                         ps.note = "Away" 
    374                         acc.setOnlineStatus(ps) 
    375                 elif label=='Busy': 
    376                         ps = pj.PresenceStatus() 
    377                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    378                         ps.activity = pj.PJRPID_ACTIVITY_BUSY 
    379                         ps.note = "Busy" 
    380                         acc.setOnlineStatus(ps) 
    381                 elif label=='Settings...': 
    382                         self.cfgChanged = False 
    383                         dlg = accountsetting.Dialog(self.master, acc.cfg) 
    384                         if dlg.doModal(): 
    385                                 self.updateAccount(acc) 
    386                                 acc.modify(acc.cfg) 
    387                 elif label=='Delete...': 
    388                         msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri 
    389                         if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
    390                                 return 
    391                         iid = str(acc.randId) 
    392                         self.accList.remove(acc) 
    393                         acc.setRegistration(False) 
    394                         acc.deleting = True 
    395                         del acc 
    396                         self.tv.delete( (iid,) ) 
    397                 elif label=='Add buddy...': 
    398                         cfg = pj.BuddyConfig() 
    399                         dlg = buddy.SettingDialog(self.master, cfg) 
    400                         if dlg.doModal(): 
    401                                 self._createBuddy(acc, cfg) 
    402                 else: 
    403                         assert not ("Unknown menu " + label) 
    404          
    405         def _onBuddyContextMenu(self, label): 
    406                 bud = self._getSelectedBuddy() 
    407                 if not bud: 
    408                         return 
    409                 acc = bud.account 
    410                          
    411                 if label=='Audio call': 
    412                         chat = acc.findChat(bud.cfg.uri) 
    413                         if not chat: chat = acc.newChat(bud.cfg.uri) 
    414                         chat.showWindow() 
    415                         chat.startCall() 
    416                 elif label=='Send instant message': 
    417                         chat = acc.findChat(bud.cfg.uri) 
    418                         if not chat: chat = acc.newChat(bud.cfg.uri) 
    419                         chat.showWindow(True) 
    420                 elif label=='Subscribe': 
    421                         bud.subscribePresence(True) 
    422                 elif label=='Unsubscribe': 
    423                         bud.subscribePresence(False) 
    424                 elif label=='Settings...': 
    425                         subs = bud.cfg.subscribe 
    426                         uri  = bud.cfg.uri 
    427                         dlg = buddy.SettingDialog(self.master, bud.cfg) 
    428                         if dlg.doModal(): 
    429                                 self.updateBuddy(bud) 
    430                                 # URI updated? 
    431                                 if uri != bud.cfg.uri: 
    432                                         cfg = bud.cfg 
    433                                         # del old 
    434                                         iid = 'buddy' + str(bud.randId) 
    435                                         acc.buddyList.remove(bud) 
    436                                         del bud 
    437                                         self.tv.delete( (iid,) ) 
    438                                         # add new 
    439                                         self._createBuddy(acc, cfg) 
    440                                 # presence subscribe setting updated 
    441                                 elif subs != bud.cfg.subscribe: 
    442                                         bud.subscribePresence(bud.cfg.subscribe) 
    443                 elif label=='Delete...': 
    444                         msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri 
    445                         if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes': 
    446                                 return 
    447                         iid = 'buddy' + str(bud.randId) 
    448                         acc.buddyList.remove(bud) 
    449                         del bud 
    450                         self.tv.delete( (iid,) ) 
    451                 else: 
    452                         assert not ("Unknown menu " + label) 
    453                          
    454         def _onTimer(self): 
    455                 if not self.quitting: 
    456                         self.ep.libHandleEvents(10) 
    457                         if not self.quitting: 
    458                                 self.master.after(50, self._onTimer) 
    459                          
    460         def _onClose(self): 
    461                 self.saveConfig() 
    462                 self.quitting = True 
    463                 self.ep.libDestroy() 
    464                 self.ep = None 
    465                 self.update() 
    466                 self.quit() 
    467                  
    468         def _onMenuAddAccount(self): 
    469                 cfg = pj.AccountConfig() 
    470                 dlg = accountsetting.Dialog(self.master, cfg) 
    471                 if dlg.doModal(): 
    472                         self._createAcc(cfg) 
    473                          
    474         def _onMenuShowHideLogWindow(self): 
    475                 if self.showLogWindow.get(): 
    476                         self.logWindow.deiconify() 
    477                 else: 
    478                         self.logWindow.withdraw() 
    479          
    480         def _onMenuSettings(self): 
    481                 dlg = settings.Dialog(self, self.appConfig) 
    482                 if dlg.doModal(): 
    483                         msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') 
    484          
    485         def _onMenuSaveSettings(self): 
    486                 self.saveConfig() 
    487                  
    488         def _onMenuQuit(self): 
    489                 self._onClose() 
    490  
    491         def _onMenuAbout(self): 
    492                 msgbox.showinfo(self.master.title(), 'About') 
    493                  
     51    """ 
     52    The Application main frame. 
     53    """ 
     54    def __init__(self): 
     55        global USE_THREADS 
     56        ttk.Frame.__init__(self, name='application', width=300, height=500) 
     57        self.pack(expand='yes', fill='both') 
     58        self.master.title('pjsua2 Demo') 
     59        self.master.geometry('500x500+100+100') 
     60 
     61        # Logger 
     62        self.logger = log.Logger() 
     63 
     64        # Accounts 
     65        self.accList = [] 
     66 
     67        # GUI variables 
     68        self.showLogWindow = tk.IntVar(value=0) 
     69        self.quitting = False 
     70 
     71        # Construct GUI 
     72        self._createWidgets() 
     73 
     74        # Log window 
     75        self.logWindow = log.LogWindow(self) 
     76        self._onMenuShowHideLogWindow() 
     77 
     78        # Instantiate endpoint 
     79        self.ep = endpoint.Endpoint() 
     80        self.ep.libCreate() 
     81 
     82        # Default config 
     83        self.appConfig = settings.AppConfig() 
     84        if USE_THREADS: 
     85            self.appConfig.epConfig.uaConfig.threadCnt = 1 
     86            self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
     87        else: 
     88            self.appConfig.epConfig.uaConfig.threadCnt = 0 
     89            self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     90        self.appConfig.epConfig.logConfig.writer = self.logger 
     91        self.appConfig.epConfig.logConfig.filename = "pygui.log" 
     92        self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND 
     93        self.appConfig.epConfig.logConfig.level = 5 
     94        self.appConfig.epConfig.logConfig.consoleLevel = 5 
     95 
     96    def saveConfig(self, filename='pygui.js'): 
     97        # Save disabled accounts since they are not listed in self.accList 
     98        disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled] 
     99        self.appConfig.accounts = [] 
     100 
     101        # Get account configs from active accounts 
     102        for acc in self.accList: 
     103            acfg = settings.AccConfig() 
     104            acfg.enabled = True 
     105            acfg.config = acc.cfg 
     106            for bud in acc.buddyList: 
     107                acfg.buddyConfigs.append(bud.cfg) 
     108            self.appConfig.accounts.append(acfg) 
     109 
     110        # Put back disabled accounts 
     111        self.appConfig.accounts.extend(disabled_accs) 
     112        # Save 
     113        self.appConfig.saveFile(filename) 
     114 
     115    def start(self, cfg_file='pygui.js'): 
     116        global USE_THREADS 
     117        # Load config 
     118        if cfg_file and os.path.exists(cfg_file): 
     119            self.appConfig.loadFile(cfg_file) 
     120 
     121        if USE_THREADS: 
     122            self.appConfig.epConfig.uaConfig.threadCnt = 1 
     123            self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
     124        else: 
     125            self.appConfig.epConfig.uaConfig.threadCnt = 0 
     126            self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     127        self.appConfig.epConfig.uaConfig.threadCnt = 0 
     128        self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     129        self.appConfig.epConfig.logConfig.writer = self.logger 
     130        self.appConfig.epConfig.logConfig.level = 5 
     131        self.appConfig.epConfig.logConfig.consoleLevel = 5 
     132 
     133        # Initialize library 
     134        self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full; 
     135        self.ep.libInit(self.appConfig.epConfig) 
     136        self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) 
     137 
     138        # Create transports 
     139        if self.appConfig.udp.enabled: 
     140            self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config) 
     141        if self.appConfig.tcp.enabled: 
     142            self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config) 
     143        if self.appConfig.tls.enabled: 
     144            self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) 
     145 
     146        # Add accounts 
     147        for cfg in self.appConfig.accounts: 
     148            if cfg.enabled: 
     149                self._createAcc(cfg.config) 
     150                acc = self.accList[-1] 
     151                for buddy_cfg in cfg.buddyConfigs: 
     152                    self._createBuddy(acc, buddy_cfg) 
     153 
     154        # Start library 
     155        self.ep.libStart() 
     156 
     157        # Start polling 
     158        if not USE_THREADS: 
     159            self._onTimer() 
     160 
     161    def updateAccount(self, acc): 
     162        if acc.deleting: 
     163            return      # ignore 
     164        iid = str(acc.randId) 
     165        text = acc.cfg.idUri 
     166        status = acc.statusText() 
     167 
     168        values = (status,) 
     169        if self.tv.exists(iid): 
     170            self.tv.item(iid, text=text, values=values) 
     171        else: 
     172            self.tv.insert('', 'end',  iid, open=True, text=text, values=values) 
     173 
     174    def updateBuddy(self, bud): 
     175        iid = 'buddy' + str(bud.randId) 
     176        text = bud.cfg.uri 
     177        status = bud.statusText() 
     178 
     179        values = (status,) 
     180        if self.tv.exists(iid): 
     181            self.tv.item(iid, text=text, values=values) 
     182        else: 
     183            self.tv.insert(str(bud.account.randId), 'end',  iid, open=True, text=text, values=values) 
     184 
     185    def _createAcc(self, acc_cfg): 
     186        acc = account.Account(self) 
     187        acc.cfg = acc_cfg 
     188        self.accList.append(acc) 
     189        self.updateAccount(acc) 
     190        acc.create(acc.cfg) 
     191        acc.cfgChanged = False 
     192        self.updateAccount(acc) 
     193 
     194    def _createBuddy(self, acc, buddy_cfg): 
     195        bud = buddy.Buddy(self) 
     196        bud.cfg = buddy_cfg 
     197        bud.account = acc 
     198        bud.create(acc, bud.cfg) 
     199        self.updateBuddy(bud) 
     200        acc.buddyList.append(bud) 
     201 
     202    def _createWidgets(self): 
     203        self._createAppMenu() 
     204 
     205        # Main pane, a Treeview 
     206        self.tv = ttk.Treeview(self, columns=('Status'), show='tree') 
     207        self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) 
     208 
     209        self._createContextMenu() 
     210 
     211        # Handle close event 
     212        self.master.protocol("WM_DELETE_WINDOW", self._onClose) 
     213 
     214    def _createAppMenu(self): 
     215        # Main menu bar 
     216        top = self.winfo_toplevel() 
     217        self.menubar = tk.Menu() 
     218        top.configure(menu=self.menubar) 
     219 
     220        # File menu 
     221        file_menu = tk.Menu(self.menubar, tearoff=False) 
     222        self.menubar.add_cascade(label="File", menu=file_menu) 
     223        file_menu.add_command(label="Add account..", command=self._onMenuAddAccount) 
     224        file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow) 
     225        file_menu.add_separator() 
     226        file_menu.add_command(label="Settings...", command=self._onMenuSettings) 
     227        file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings) 
     228        file_menu.add_separator() 
     229        file_menu.add_command(label="Quit", command=self._onMenuQuit) 
     230 
     231        # Window menu 
     232        self.window_menu = tk.Menu(self.menubar, tearoff=False) 
     233        self.menubar.add_cascade(label="Window", menu=self.window_menu) 
     234 
     235        # Help menu 
     236        help_menu = tk.Menu(self.menubar, tearoff=False) 
     237        self.menubar.add_cascade(label="Help", menu=help_menu) 
     238        help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) 
     239 
     240    def _showChatWindow(self, chat_inst): 
     241        chat_inst.showWindow() 
     242 
     243    def updateWindowMenu(self): 
     244        # Chat windows 
     245        self.window_menu.delete(0, tk.END) 
     246        for acc in self.accList: 
     247            for c in acc.chatList: 
     248                cmd = lambda arg=c: self._showChatWindow(arg) 
     249                self.window_menu.add_command(label=c.title, command=cmd) 
     250 
     251    def _createContextMenu(self): 
     252        top = self.winfo_toplevel() 
     253 
     254        # Create Account context menu 
     255        self.accMenu = tk.Menu(top, tearoff=False) 
     256        # Labels, must match with _onAccContextMenu() 
     257        labels = ['Unregister', 'Reregister', 'Add buddy...', '-', 
     258              'Online', 'Invisible', 'Away', 'Busy', '-', 
     259              'Settings...', '-', 
     260              'Delete...'] 
     261        for label in labels: 
     262            if label=='-': 
     263                self.accMenu.add_separator() 
     264            else: 
     265                cmd = lambda arg=label: self._onAccContextMenu(arg) 
     266                self.accMenu.add_command(label=label, command=cmd) 
     267 
     268        # Create Buddy context menu 
     269        # Labels, must match with _onBuddyContextMenu() 
     270        self.buddyMenu = tk.Menu(top, tearoff=False) 
     271        labels = ['Audio call', 'Send instant message', '-', 
     272              'Subscribe', 'Unsubscribe', '-', 
     273              'Settings...', '-', 
     274              'Delete...'] 
     275 
     276        for label in labels: 
     277            if label=='-': 
     278                self.buddyMenu.add_separator() 
     279            else: 
     280                cmd = lambda arg=label: self._onBuddyContextMenu(arg) 
     281                self.buddyMenu.add_command(label=label, command=cmd) 
     282 
     283        if (top.tk.call('tk', 'windowingsystem')=='aqua'): 
     284            self.tv.bind('<2>', self._onTvRightClick) 
     285            self.tv.bind('<Control-1>', self._onTvRightClick) 
     286        else: 
     287            self.tv.bind('<3>', self._onTvRightClick) 
     288        self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) 
     289 
     290    def _getSelectedAccount(self): 
     291        items = self.tv.selection() 
     292        if not items: 
     293            return None 
     294        try: 
     295            iid = int(items[0]) 
     296        except: 
     297            return None 
     298        accs = [acc for acc in self.accList if acc.randId==iid] 
     299        if not accs: 
     300            return None 
     301        return accs[0] 
     302 
     303    def _getSelectedBuddy(self): 
     304        items = self.tv.selection() 
     305        if not items: 
     306            return None 
     307        try: 
     308            iid = int(items[0][5:]) 
     309            iid_parent = int(self.tv.parent(items[0])) 
     310        except: 
     311            return None 
     312 
     313        accs = [acc for acc in self.accList if acc.randId==iid_parent] 
     314        if not accs: 
     315            return None 
     316 
     317        buds = [b for b in accs[0].buddyList if b.randId==iid] 
     318        if not buds: 
     319            return None 
     320 
     321        return buds[0] 
     322 
     323    def _onTvRightClick(self, event): 
     324        iid = self.tv.identify_row(event.y) 
     325        #iid = self.tv.identify('item', event.x, event.y) 
     326        if iid: 
     327            self.tv.selection_set( (iid,) ) 
     328            acc = self._getSelectedAccount() 
     329            if acc: 
     330                self.accMenu.post(event.x_root, event.y_root) 
     331            else: 
     332                # A buddy is selected 
     333                self.buddyMenu.post(event.x_root, event.y_root) 
     334 
     335    def _onTvDoubleClick(self, event): 
     336        iid = self.tv.identify_row(event.y) 
     337        if iid: 
     338            self.tv.selection_set( (iid,) ) 
     339            acc = self._getSelectedAccount() 
     340            if acc: 
     341                self.cfgChanged = False 
     342                dlg = accountsetting.Dialog(self.master, acc.cfg) 
     343                if dlg.doModal(): 
     344                    self.updateAccount(acc) 
     345                    acc.modify(acc.cfg) 
     346            else: 
     347                bud = self._getSelectedBuddy() 
     348                acc = bud.account 
     349                chat = acc.findChat(bud.cfg.uri) 
     350                if not chat: 
     351                    chat = acc.newChat(bud.cfg.uri) 
     352                chat.showWindow() 
     353 
     354    def _onAccContextMenu(self, label): 
     355        acc = self._getSelectedAccount() 
     356        if not acc: 
     357            return 
     358 
     359        if label=='Unregister': 
     360            acc.setRegistration(False) 
     361        elif label=='Reregister': 
     362            acc.setRegistration(True) 
     363        elif label=='Online': 
     364            ps = pj.PresenceStatus() 
     365            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     366            acc.setOnlineStatus(ps) 
     367        elif label=='Invisible': 
     368            ps = pj.PresenceStatus() 
     369            ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE 
     370            acc.setOnlineStatus(ps) 
     371        elif label=='Away': 
     372            ps = pj.PresenceStatus() 
     373            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     374            ps.activity = pj.PJRPID_ACTIVITY_AWAY 
     375            ps.note = "Away" 
     376            acc.setOnlineStatus(ps) 
     377        elif label=='Busy': 
     378            ps = pj.PresenceStatus() 
     379            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     380            ps.activity = pj.PJRPID_ACTIVITY_BUSY 
     381            ps.note = "Busy" 
     382            acc.setOnlineStatus(ps) 
     383        elif label=='Settings...': 
     384            self.cfgChanged = False 
     385            dlg = accountsetting.Dialog(self.master, acc.cfg) 
     386            if dlg.doModal(): 
     387                self.updateAccount(acc) 
     388                acc.modify(acc.cfg) 
     389        elif label=='Delete...': 
     390            msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri 
     391            if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
     392                return 
     393            iid = str(acc.randId) 
     394            self.accList.remove(acc) 
     395            acc.setRegistration(False) 
     396            acc.deleting = True 
     397            del acc 
     398            self.tv.delete( (iid,) ) 
     399        elif label=='Add buddy...': 
     400            cfg = pj.BuddyConfig() 
     401            dlg = buddy.SettingDialog(self.master, cfg) 
     402            if dlg.doModal(): 
     403                self._createBuddy(acc, cfg) 
     404        else: 
     405            assert not ("Unknown menu " + label) 
     406 
     407    def _onBuddyContextMenu(self, label): 
     408        bud = self._getSelectedBuddy() 
     409        if not bud: 
     410            return 
     411        acc = bud.account 
     412 
     413        if label=='Audio call': 
     414            chat = acc.findChat(bud.cfg.uri) 
     415            if not chat: chat = acc.newChat(bud.cfg.uri) 
     416            chat.showWindow() 
     417            chat.startCall() 
     418        elif label=='Send instant message': 
     419            chat = acc.findChat(bud.cfg.uri) 
     420            if not chat: chat = acc.newChat(bud.cfg.uri) 
     421            chat.showWindow(True) 
     422        elif label=='Subscribe': 
     423            bud.subscribePresence(True) 
     424        elif label=='Unsubscribe': 
     425            bud.subscribePresence(False) 
     426        elif label=='Settings...': 
     427            subs = bud.cfg.subscribe 
     428            uri  = bud.cfg.uri 
     429            dlg = buddy.SettingDialog(self.master, bud.cfg) 
     430            if dlg.doModal(): 
     431                self.updateBuddy(bud) 
     432                # URI updated? 
     433                if uri != bud.cfg.uri: 
     434                    cfg = bud.cfg 
     435                    # del old 
     436                    iid = 'buddy' + str(bud.randId) 
     437                    acc.buddyList.remove(bud) 
     438                    del bud 
     439                    self.tv.delete( (iid,) ) 
     440                    # add new 
     441                    self._createBuddy(acc, cfg) 
     442                # presence subscribe setting updated 
     443                elif subs != bud.cfg.subscribe: 
     444                    bud.subscribePresence(bud.cfg.subscribe) 
     445        elif label=='Delete...': 
     446            msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri 
     447            if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes': 
     448                return 
     449            iid = 'buddy' + str(bud.randId) 
     450            acc.buddyList.remove(bud) 
     451            del bud 
     452            self.tv.delete( (iid,) ) 
     453        else: 
     454            assert not ("Unknown menu " + label) 
     455 
     456    def _onTimer(self): 
     457        if not self.quitting: 
     458            self.ep.libHandleEvents(10) 
     459            if not self.quitting: 
     460                self.master.after(50, self._onTimer) 
     461 
     462    def _onClose(self): 
     463        self.saveConfig() 
     464        self.quitting = True 
     465        self.ep.libDestroy() 
     466        self.ep = None 
     467        self.update() 
     468        self.quit() 
     469 
     470    def _onMenuAddAccount(self): 
     471        cfg = pj.AccountConfig() 
     472        dlg = accountsetting.Dialog(self.master, cfg) 
     473        if dlg.doModal(): 
     474            self._createAcc(cfg) 
     475 
     476    def _onMenuShowHideLogWindow(self): 
     477        if self.showLogWindow.get(): 
     478            self.logWindow.deiconify() 
     479        else: 
     480            self.logWindow.withdraw() 
     481 
     482    def _onMenuSettings(self): 
     483        dlg = settings.Dialog(self, self.appConfig) 
     484        if dlg.doModal(): 
     485            msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') 
     486 
     487    def _onMenuSaveSettings(self): 
     488        self.saveConfig() 
     489 
     490    def _onMenuQuit(self): 
     491        self._onClose() 
     492 
     493    def _onMenuAbout(self): 
     494        msgbox.showinfo(self.master.title(), 'About') 
     495 
    494496 
    495497class ExceptionCatcher: 
    496         """Custom Tk exception catcher, mainly to display more information  
    497            from pj.Error exception 
    498         """  
    499         def __init__(self, func, subst, widget): 
    500                 self.func = func  
    501                 self.subst = subst 
    502                 self.widget = widget 
    503         def __call__(self, *args): 
    504                 try: 
    505                         if self.subst: 
    506                                 args = apply(self.subst, args) 
    507                         return apply(self.func, args) 
    508                 except pj.Error, error: 
    509                         print 'Exception:' 
    510                         print '  ', error.info() 
    511                         print 'Traceback:' 
    512                         print traceback.print_stack() 
    513                         log.writeLog2(1, 'Exception: ' + error.info() + '\n') 
    514                 except Exception, error: 
    515                         print 'Exception:' 
    516                         print '  ', str(error) 
    517                         print 'Traceback:' 
    518                         print traceback.print_stack() 
    519                         log.writeLog2(1, 'Exception: ' + str(error) + '\n') 
     498    """Custom Tk exception catcher, mainly to display more information 
     499       from pj.Error exception 
     500    """ 
     501    def __init__(self, func, subst, widget): 
     502        self.func = func 
     503        self.subst = subst 
     504        self.widget = widget 
     505    def __call__(self, *args): 
     506        try: 
     507            if self.subst: 
     508                args = apply(self.subst, args) 
     509            return apply(self.func, args) 
     510        except pj.Error as error: 
     511            write("Exception:\r\n") 
     512            write("  ," + error.info() + "\r\n") 
     513            write("Traceback:\r\n") 
     514            write(traceback.print_stack()) 
     515            log.writeLog2(1, 'Exception: ' + error.info() + '\n') 
     516        except Exception as error: 
     517            write("Exception:\r\n") 
     518            write("  ," +  str(error) + "\r\n") 
     519            write("Traceback:\r\n") 
     520            write(traceback.print_stack()) 
     521            log.writeLog2(1, 'Exception: ' + str(error) + '\n') 
    520522 
    521523def main(): 
    522         #tk.CallWrapper = ExceptionCatcher 
    523         app = Application() 
    524         app.start() 
    525         app.mainloop() 
    526                  
     524    #tk.CallWrapper = ExceptionCatcher 
     525    app = Application() 
     526    app.start() 
     527    app.mainloop() 
     528 
    527529if __name__ == '__main__': 
    528         main() 
     530    main() 
Note: See TracChangeset for help on using the changeset viewer.