This URL has Read-Only access.

Statistics
| Branch: | Tag: | Revision:

root / py / maugis / scenic / gui.py @ e21a6d08

History | View | Annotate | Download (30.5 kB)

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# 
4
# Scenic
5
# Copyright (C) 2008 Société des arts technologiques (SAT)
6
# http://www.sat.qc.ca
7
# All rights reserved.
8
#
9
# This file is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# Scenic is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with Scenic. If not, see <http://www.gnu.org/licenses/>.
21

    
22
"""
23
Scenic GTK GUI.
24

25
Negotiation is done as follow:
26
------------------------------
27
 * {"msg":"INVITE", "videoport":10000, "audioport":11000, "sid":0, "please_send_to_port":999}
28
   * Each peer ask for ports to send to, and of media settings as well. "video": [{"port":10000, "codec":"mpeg4", "bitrate":3000000}]
29
 * {"msg":"ACCEPT", "videoport":10000, "audioport":11000, "sid":0}
30
 * {"msg":"REFUSE", "sid":0}
31
 * {"msg":"CANCEL", "sid":0}
32
 * {"msg":"ACK", "sid":0}
33
 * {"msg":"BYE", "sid":0}
34
 * {"msg":"OK", "sid":0}
35

36
Former Notes
37
------------
38
 * bug pour setter le bouton par defaut quand on change de tab. Il faut que le tab est le focus pour que ca marche. Pourtant le "print" apparait ???
39
"""
40
### CONSTANTS ###
41
from scenic import version
42
__version__ = version.__version__
43
APP_NAME = "scenic"
44

    
45
### MODULES IMPORTS  ###
46

    
47
import sys
48
import os
49
import smtplib
50
import gtk.glade
51
import webbrowser
52
import gettext
53

    
54
from twisted.internet import reactor
55

    
56
from scenic import process # just for constants
57
from scenic import dialogs
58
from scenic import data
59

    
60
PACKAGE_DATA = os.path.dirname(data.__file__)
61

    
62
### MULTILINGUAL SUPPORT ###
63
_ = gettext.gettext
64
gettext.bindtextdomain(APP_NAME, os.path.join(PACKAGE_DATA, "locale"))
65
gettext.textdomain(APP_NAME)
66
gtk.glade.bindtextdomain(APP_NAME, os.path.join(PACKAGE_DATA, "locale"))
67
gtk.glade.textdomain(APP_NAME)
68

    
69
def _get_combobox_value(widget):
70
    """
71
    Returns the current value of a GTK ComboBox widget.
72
    """
73
    index = widget.get_active()
74
    tree_model = widget.get_model()
75
    tree_model_row = tree_model[index]
76
    return tree_model_row[0] 
77

    
78
def _set_combobox_value(widget, value=None):
79
    """
80
    Sets the current value of a GTK ComboBox widget.
81
    """
82
    index = None
83
    tree_model = widget.get_model()
84
    index = 0
85
    for i in iter(tree_model):
86
        v = i[0]
87
        if v == value:
88
            break # got it
89
        index += 1
90
    if index is None:
91
        widget.set_active(-1)
92
    else:
93
        widget.set_active(index)
94

    
95
# GUI value to milhouse value mapping:
96
VIDEO_CODECS = {
97
    "h.264": "h264",
98
    "h.263": "h263",
99
    "Theora": "theora",
100
    "MPEG4": "mpeg4"
101
    }
102

    
103
def format_contact_markup(contact):
104
    """
105
    Formats a contact for the Adressbook GTK widget.
106
    @param contact: A dict with keys "name", "address" and "port"
107
    @rettype: str
108
    @return: Pango markup for the TreeView widget.
109
    """
110
    return "<b>%s</b>\n  IP: %s\n  Port: %s" % (contact["name"], contact["address"], contact["port"])
111

    
112
ABOUT_LABEL = """<b><big>Scenic</big></b>
113
Version: %s
114
Copyright: SAT
115
Authors: Etienne Desautels, Alexandre Quessy, Tristan Matthews, Simon Piette""" % (__version__)
116

    
117
ABOUT_TEXT_VIEW = """
118
Scenic is the advanced user graphical interface for the Milhouse audio/video streamer for GNU/Linux. 
119

120
Each peer decides what to receive from the other peer. Next, one peer can invite an other one to stream high-quality audio and video.
121
"""
122

    
123
class Gui(object):
124
    """
125
    Main application (arguably God) class
126
     * Contains the main GTK window
127
    """
128
    def __init__(self, app, kiosk_mode=False, fullscreen=False):
129
        # --------------------------------------
130
        # TODO: move that stuff to the Application class
131
        self.app = app
132
        self.load_gtk_theme(self.app.config.theme)
133
        self.kiosk_mode_on = kiosk_mode
134
        # ---------------------------------------
135
        
136
        self._offerer_invite_timeout = None
137
        # Set the Glade file
138
        glade_file = os.path.join(PACKAGE_DATA, 'scenic.glade')
139
        if os.path.isfile(glade_file):
140
            glade_path = glade_file
141
        else:
142
            text = _("<b><big>Could not find the Glade file?</big></b>\n\n" \
143
                    "Be sure the file %s exists. Quitting.") % glade_file
144
            print(text)
145
            sys.exit()
146
        self.widgets = gtk.glade.XML(glade_path, domain=APP_NAME)
147
        
148
        # connects callbacks to widgets automatically
149
        cb = {}
150
        for n in dir(self.__class__):
151
            if n[0] != '_' and hasattr(self, n):
152
                cb[n] = getattr(self, n)
153
        self.widgets.signal_autoconnect(cb)
154

    
155
        # Get all the widgets that we use
156
        self.main_window = self.widgets.get_widget("main_window")
157
        self.main_window.connect('delete-event', self.on_main_window_deleted)
158
        self.main_window.set_icon_from_file(os.path.join(PACKAGE_DATA, 'scenic.png'))
159
        self.main_tabs_widget = self.widgets.get_widget("mainTabs")
160
        self.system_tab_contents_widget = self.widgets.get_widget("system_tab_contents")
161
        self.main_window.connect("window-state-event", self.on_window_state_event)
162
        # confirm_dialog:
163
        self.confirm_dialog = self.widgets.get_widget("confirm_dialog")
164
        self.confirm_dialog.connect('delete-event', self.confirm_dialog.hide_on_delete)
165
        self.confirm_dialog.set_transient_for(self.main_window)
166
        self.confirm_label = self.widgets.get_widget("confirm_label")
167
        # calling_dialog:
168
        self.calling_dialog = self.widgets.get_widget("calling_dialog")
169
        self.calling_dialog.connect('delete-event', self.on_invite_contact_cancelled)
170
        # error_dialog:
171
        self.error_dialog = self.widgets.get_widget("error_dialog")
172
        self.error_dialog.connect('delete-event', self.error_dialog.hide_on_delete)
173
        self.error_dialog.set_transient_for(self.main_window)
174
        # Could not connect:
175
        self.error_label_widget = self.widgets.get_widget("error_dialog_label")
176
        # invited_dialog:
177
        self.invited_dialog = self.widgets.get_widget("invited_dialog")
178
        self.invited_dialog.set_transient_for(self.main_window)
179
        self.invited_dialog.connect('delete-event', self.invited_dialog.hide_on_delete)
180
        self.invited_dialog_label_widget = self.widgets.get_widget("invited_dialog_label")
181
        # edit_contact_window:
182
        self.edit_contact_window = self.widgets.get_widget("edit_contact_window")
183
        self.edit_contact_window.set_transient_for(self.main_window) # child of main window
184
        self.edit_contact_window.connect('delete-event', self.edit_contact_window.hide_on_delete)
185
        self.contact_name_widget = self.widgets.get_widget("contact_name")
186
        self.contact_addr_widget = self.widgets.get_widget("contact_addr")
187
        self.contact_port_widget = self.widgets.get_widget("contact_port")
188
        # address book buttons and list:
189
        self.edit_contact_widget = self.widgets.get_widget("edit_contact")
190
        self.remove_contact_widget = self.widgets.get_widget("remove_contact")
191
        self.invite_contact_widget = self.widgets.get_widget("invite_contact")
192
        self.contact_list_widget = self.widgets.get_widget("contact_list")
193
        # position of currently selected contact in list of contact:
194
        self.selected_contact_row = None
195
        self.select_contact_index = None
196
        # video tab drop-down menus
197
        self.video_size_widget = self.widgets.get_widget("video_size")
198
        self.video_display_widget = self.widgets.get_widget("video_display")
199
        self.video_bitrate_widget = self.widgets.get_widget("video_bitrate")
200
        self.video_source_widget = self.widgets.get_widget("video_source")
201
        self.video_codec_widget = self.widgets.get_widget("video_codec")
202
        self.video_view_preview_widget = self.widgets.get_widget("video_view_preview")
203
        # about tab contents:
204
        self.about_label_widget = self.widgets.get_widget("about_label")
205
        self.about_text_view_widget = self.widgets.get_widget("about_text_view")
206
            
207
        # switch to Kiosk mode if asked
208
        if self.kiosk_mode_on:
209
            self.main_window.set_decorated(False)
210
        else:
211
            # Removes the sytem_tab 
212
            tab_num = self.main_tabs_widget.page_num(self.system_tab_contents_widget)
213
            print "Removing tab #", tab_num
214
            self.main_tabs_widget.remove_page(tab_num)
215
        
216
        self.is_fullscreen = False
217
        if fullscreen:
218
            self.toggle_fullscreen()
219
        
220
        # Build the contact list view
221
        self.selection = self.contact_list_widget.get_selection()
222
        self.selection.connect("changed", self.on_contact_list_changed, None) 
223
        self.contact_tree = gtk.ListStore(str)
224
        self.contact_list_widget.set_model(self.contact_tree)
225
        column = gtk.TreeViewColumn(_("Contacts"), gtk.CellRendererText(), markup=0)
226
        self.contact_list_widget.append_column(column)
227
        # set value of widgets.
228
        # TODO: get rid of those methods
229
        self._init_widgets_value() # XXX
230

    
231
        self.main_window.show()
232
   
233
    # ------------------ window events and actions --------------------
234

    
235
    def load_gtk_theme(self, name="Darklooks"):
236
        file_name = os.path.join(PACKAGE_DATA, "themes/%s/gtk-2.0/gtkrc" % (name))
237
        #if file_name is None:
238
        #    file_name = os.path.join(PACKAGE_DATA, "themes/Darklooks/gtk-2.0/gtkrc")
239
        # FIXME: not able to reload themes dynamically.
240
        if os.path.exists(file_name):
241
            #os.environ["GTK2_RC_FILES"] = file_name
242
            gtk.rc_parse(file_name)
243
            #gtk.rc_reset_styles(gtk.settings_get_default())
244
            #print "loading theme", file_name
245
            #gtk.rc_parse(file_name)
246
            #gtk.rc_reparse_all()
247
        else:
248
            print("File name not found: %s" % (file_name))
249
     
250
    def toggle_fullscreen(self):
251
        """
252
        Toggles the fullscreen mode on/off.
253
        """
254
        if self.is_fullscreen:
255
            self.main_window.unfullscreen()
256
        else:
257
            self.main_window.fullscreen()
258
    
259
    def on_window_state_event(self, widget, event):
260
        """
261
        Called when toggled fullscreen.
262
        """
263
        self.is_fullscreen = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN != 0
264
        print('fullscreen %s' % (self.is_fullscreen))
265
        return True
266
    
267
    def on_main_window_deleted(self, *args):
268
        """
269
        Destroy method causes appliaction to exit
270
        when main window closed
271
        """
272
        return self._confirm_and_quit()
273
        
274
    def _confirm_and_quit(self):
275
        def _cb(result):
276
            if result:
277
                print("Destroying the window.")
278
                self.main_window.destroy()
279
            else:
280
                print("Not quitting.")
281
        # If you return FALSE in the "delete_event" signal handler,
282
        # GTK will emit the "destroy" signal. Returning TRUE means
283
        # you don't want the window to be destroyed.
284
        # This is useful for popping up 'are you sure you want to quit?'
285
        # type dialogs. 
286
        if self.app.config.confirm_quit:
287
            d = dialogs.YesNoDialog.create("Really quit ?\nAll streaming processes will quit as well.\nMake sure to save your settings if desired.", parent=self.main_window)
288
            d.addCallback(_cb)
289
            return True
290
        else:
291
            _cb(True)
292
            return False
293
    
294
    def on_main_window_destroyed(self, *args):
295
        # TODO: confirm dialog!
296
        if reactor.running:
297
            print("reactor.stop()")
298
            reactor.stop()
299

    
300
    # --------------- slots for some widget events ------------
301

    
302
    def on_video_view_preview_toggled(self, widget):
303
        """
304
        Shows a preview of the video input.
305
        """
306
        #TODO: create a new process protocol for the preview window
307
        #TODO: stop it when starting to stream, if running
308
        #TODO: stop it when button is toggled to false.
309
        # It can be the user that pushed the button, or it can be toggled by the software.
310
        print 'video_view_preview toggled', widget.get_active()
311
        if widget.get_active():
312
            command = "milhouse --videosource v4l2src --videodevice %s --localvideo --window-title preview" % (self.app.config.video_device)
313
            print "spawning", command
314
            process.run_once(*command.split())
315
            dialogs.ErrorDialog.create("You must manually close the preview window.", parent=self.main_window)
316
        else:
317
            print "stopping preview"
318

    
319
    def on_main_tabs_switch_page(self, widget, notebook_page, page_number):
320
        tab = widget.get_nth_page(page_number)
321
        if tab == "localPan":
322
            self.widgets.get_widget("network_admin").grab_default() # FIXME
323
        elif tab == "contactPan":
324
            self.widgets.get_widget("contactJoinBut").grab_default() # FIXME
325

    
326
    def on_contact_list_changed(self, *args):
327
        tree_list, self.selected_contact_row = args[0].get_selected()
328
        if self.selected_contact_row:
329
            self.edit_contact_widget.set_sensitive(True)
330
            self.remove_contact_widget.set_sensitive(True)
331
            self.invite_contact_widget.set_sensitive(True)
332
            self.selected_contact_index = tree_list.get_path(self.selected_contact_row)[0]
333
            self.app.address_book.selected_contact = self.app.address_book.contact_list[self.selected_contact_index]
334
            self.app.address_book.selected_index = self.selected_contact_index
335
        else:
336
            self.edit_contact_widget.set_sensitive(False)
337
            self.remove_contact_widget.set_sensitive(False)
338
            self.invite_contact_widget.set_sensitive(False)
339
            self.app.address_book.selected_contact = None
340

    
341
    # ---------------------- slots for addressbook widgets events --------
342
    
343
    def on_contact_double_clicked(self, *args):
344
        """
345
        When a contact in the list is double-clicked, 
346
        shows the edit contact dialog.
347
        """
348
        self.on_edit_contact_clicked(args)
349

    
350
    def on_add_contact_clicked(self, *args):
351
        """
352
        Pops up a dialog to be filled with new contact infos.
353
        
354
        The add_contact buttons has been clicked.
355
        """
356
        self.app.address_book.current_contact_is_new = True
357
        # Update the text in the edit/new contact dialog:
358
        self.contact_name_widget.set_text("")
359
        self.contact_addr_widget.set_text("")
360
        self.contact_port_widget.set_text("")
361
        self.edit_contact_window.show()
362

    
363
    def on_remove_contact_clicked(self, *args):
364
        """
365
        Upon confirmation, the selected contact is removed.
366
        """
367
        def on_confirm_result(result):
368
            if result:
369
                del self.app.address_book.contact_list[self.selected_contact_index]
370
                self.contact_tree.remove(self.selected_contact_row)
371
                num = self.selected_contact_index - 1
372
                if num < 0:
373
                    num = 0
374
                self.selection.select_path(num)
375
        text = _("<b><big>Delete this contact from the list?</big></b>\n\nAre you sure you want "
376
            "to delete this contact from the list?")
377
        self.show_confirm_dialog(text, on_confirm_result)
378

    
379
    def on_edit_contact_clicked(self, *args):
380
        """
381
        Shows the edit contact dialog.
382
        """
383
        self.contact_name_widget.set_text(self.app.address_book.selected_contact["name"])
384
        self.contact_addr_widget.set_text(self.app.address_book.selected_contact["address"])
385
        self.contact_port_widget.set_text(str(self.app.address_book.selected_contact["port"]))
386
        self.edit_contact_window.show() # addr
387

    
388
    def on_edit_contact_cancel_clicked(self, *args):
389
        """
390
        The cancel button in the "edit_contact" window has been clicked.
391
        Hides the edit_contact window.
392
        """
393
        self.edit_contact_window.hide()
394

    
395
    def on_edit_contact_save_clicked(self, *args):
396
        """
397
        The save button in the "edit_contact" window has been clicked.
398
        Hides the edit_contact window and saves the changes. (new or modified contact)
399
        """
400
        def when_valid_save():
401
            """ Saves contact info after it's been validated and then closes the window"""
402
            contact = {
403
                "name": self.contact_name_widget.get_text(),
404
                "address": addr, 
405
                "port": int(port)
406
                }
407
            contact_markup = format_contact_markup(contact)
408
            if self.app.address_book.current_contact_is_new:
409
                self.contact_tree.append([contact_markup]) # add it to the tree list
410
                self.app.address_book.contact_list.append([contact_markup]) # and the internal address book
411
                self.selection.select_path(len(self.app.address_book.contact_list) - 1) # select it ...?
412
                self.app.address_book.selected_contact = self.app.address_book.contact_list[len(self.app.address_book.contact_list) - 1] #FIXME: we should not copy a dict like that
413
                self.app.address_book.current_contact_is_new = False # FIXME: what does that mean?
414
            else:
415
                self.contact_tree.set_value(self.selected_contact_row, 0, contact_markup)
416
            self.app.address_book.selected_contact = contact
417
            self.edit_contact_window.hide()
418

    
419
        # Validate the port number
420
        port = self.contact_port_widget.get_text()
421
        if port == "":
422
            port = str(self.app.config.negotiation_port) # set port to default
423
        elif not port.isdigit():
424
            text = _("The port number must be an integer.")
425
            self.show_error_dialog(text)
426
            return
427
        elif int(port) not in range(10000, 65535):
428
            text = _("The port number must be in the range of 10000-65535")
429
            self.show_error_dialog(text)
430
            return
431
        # Validate the address
432
        addr = self.contact_addr_widget.get_text()
433
        if len(addr) < 7:
434
            text = _("The address is not valid\n\nEnter a valid address\nExample: 168.123.45.32 or example.org")
435
            self.show_error_dialog(text)
436
            return
437
        # save it.
438
        when_valid_save()
439

    
440
    # ---------------------------- Custom system tab buttons ---------------
441

    
442
    def on_network_admin_clicked(self, *args):
443
        """
444
        Opens the network-admin Gnome applet.
445
        """
446
        process.run_once("gksudo", "network-admin")
447

    
448
    def on_system_shutdown_clicked(self, *args):
449
        """
450
        Shuts down the computer.
451
        """
452
        def on_confirm_result(result):
453
            if result:
454
                process.run_once("gksudo", "shutdown -h now")
455

    
456
        text = _("<b><big>Shutdown the computer?</big></b>\n\nAre you sure you want to shutdown the computer now?")
457
        self.show_confirm_dialog(text, on_confirm_result)
458

    
459
    def on_system_reboot_clicked(self, *args):
460
        """
461
        Reboots the computer.
462
        """
463
        def on_confirm_result(result):
464
            if result:
465
                process.run_once("gksudo", "shutdown -r now")
466

    
467
        text = _("<b><big>Reboot the computer?</big></b>\n\nAre you sure you want to reboot the computer now?")
468
        self.show_confirm_dialog(text, on_confirm_result)
469

    
470
    def on_maintenance_apt_update_clicked(self, *args):
471
        """
472
        Opens APT update manager.
473
        """
474
        process.run_once("gksudo", "update-manager")
475

    
476
    def on_maintenance_send_info_clicked(self, *args):
477
        """
478
        Sends an email to SAT with this information : 
479
         * milhouse version
480
         * kernel version
481
         * Loaded kernel modules
482
        """
483
        def on_confirm_result(result):
484
            milhouse_version = "unknown"
485
            if result:
486
                msg = "--- milhouse_version ---\n" + milhouse_version + "\n"
487
                msg += "--- uname -a ---\n"
488
                try:
489
                    w, r, err = os.popen3('uname -a')
490
                    msg += r.read() + "\n"
491
                    errRead = err.read()
492
                    if errRead:
493
                        msg += errRead + "\n"
494
                    w.close()
495
                    r.close()
496
                    err.close()
497
                except:
498
                    msg += "Error executing 'uname -a'\n"
499
                msg += "--- lsmod ---\n"
500
                try:
501
                    w, r, err = os.popen3('lsmod')
502
                    msg += r.read()
503
                    errRead = err.read()
504
                    if errRead:
505
                        msg += "\n" + errRead
506
                    w.close()
507
                    r.close()
508
                    err.close()
509
                except:
510
                    msg += "Error executing 'lsmod'"
511
                fromaddr = self.app.config.email_info
512
                toaddrs  = self.app.config.email_info
513
                toaddrs = toaddrs.split(', ')
514
                server = smtplib.SMTP(self.app.config.smtpserver)
515
                server.set_debuglevel(0)
516
                try:
517
                    server.sendmail(fromaddr, toaddrs, msg)
518
                except:
519
                    text = _("Could not send info.\n\nCheck your internet connection.")
520
                    self.show_error_dialog(text)
521
                server.quit()
522
        
523
        text = _("<b><big>Send the settings?</big></b>\n\nAre you sure you want to send your computer settings to the administrator of scenic?")
524
        self.show_confirm_dialog(text, on_confirm_result)
525

    
526
        
527
    # --------------------- configuration and widgets value ------------
528

    
529
    def _gather_configuration(self):
530
        """
531
        Updates the configuration with the value of each widget.
532
        """
533
        print("gathering configuration")
534
        # VIDEO SIZE
535
        video_size = _get_combobox_value(self.video_size_widget)
536
        print ' * video_size:', video_size
537
        self.app.config.video_width = int(video_size.split("x")[0])
538
        self.app.config.video_height = int(video_size.split("x")[1])
539
        
540
        # DISPLAY
541
        video_display = _get_combobox_value(self.video_display_widget)
542
        print ' * video_display:', video_display
543
        self.app.config.video_display = video_display
544
        
545
        # BITRATE
546
        video_bitrate = _get_combobox_value(self.video_bitrate_widget)
547
        print ' * video_bitrate:', video_bitrate
548
        self.app.config.video_bitrate = int(video_bitrate.split(" ")[0]) * 1000000
549
        
550
        # VIDEO SOURCE AND DEVICE
551
        video_source = _get_combobox_value(self.video_source_widget)
552
        if video_source == "Color bars":
553
            self.app.config.video_source = "videotestsrc"
554
        elif video_source.startswith("/dev/video"): # TODO: firewire!
555
            self.app.config.video_device = video_source
556
            self.app.config.video_source = "v4l2src"
557
        print ' * videosource:', video_source
558
        
559
        # CODEC
560
        video_codec = _get_combobox_value(self.video_codec_widget)
561
        self.app.config.video_codec = VIDEO_CODECS[video_codec]
562
        print ' * video_codec:', video_codec
563
        
564
        #TODO: get toggle fullscreen (milhouse) value
565

    
566
    def _init_widgets_value(self):
567
        """
568
        Called once at startup.
569
         * Once the config file is read, 
570
         * Sets the value of each widget according to the data stored in the configuration file.
571
        """
572
        print("Changing widgets value according to configuration.")
573
        # VIDEO SIZE
574
        video_size = "%sx%s" % (self.app.config.video_width, self.app.config.video_height)
575
        _set_combobox_value(self.video_size_widget, video_size)
576
        print ' * video_size:', video_size
577
        
578
        # DISPLAY
579
        video_display = self.app.config.video_display
580
        _set_combobox_value(self.video_display_widget, video_display)
581
        print ' * video_display:', video_display
582
        
583
        # BITRATE
584
        video_bitrate = "%s Mbps" % (int(self.app.config.video_bitrate) / 1000000)
585
        _set_combobox_value(self.video_bitrate_widget, video_bitrate)
586
        print ' * video_bitrate:', video_bitrate
587
        
588
        # VIDEO SOURCE AND DEVICE
589
        if self.app.config.video_source == "videotestsrc":
590
            video_source = "Color bars"
591
        elif self.app.config.video_source == "v4l2src":
592
            video_source = self.app.config.video_device
593
        _set_combobox_value(self.video_source_widget, video_source)
594
        print ' * videosource:', video_source
595

    
596
        # CODEC
597
        # gets key for a value
598
        video_codec = VIDEO_CODECS.keys()[VIDEO_CODECS.values().index(self.app.config.video_codec)]
599
        _set_combobox_value(self.video_codec_widget, video_codec)
600
        print ' * video_codec:', video_codec
601

    
602
        # ADDRESSBOOK
603
        # Init addressbook contact list:
604
        self.app.address_book.selected_contact = None
605
        self.app.address_book.current_contact_is_new = False
606
        if len(self.app.address_book.contact_list) > 0:
607
            for contact in self.app.address_book.contact_list:
608
                contact_markup = format_contact_markup(contact)
609
                self.contact_tree.append([contact_markup])
610
            self.selection.select_path(self.app.address_book.selected)
611
        else:
612
            self.edit_contact_widget.set_sensitive(False)
613
            self.remove_contact_widget.set_sensitive(False)
614
            self.invite_contact_widget.set_sensitive(False)
615

    
616
        # ABOUT TAB CONTENTS:
617
        self.about_label_widget.set_markup(ABOUT_LABEL)
618
        about_text_buffer = gtk.TextBuffer()
619
        about_text_buffer.set_text(ABOUT_TEXT_VIEW)
620
        self.about_text_view_widget.set_buffer(about_text_buffer)
621

    
622
    # -------------------------- menu items -----------------
623
    
624
    def on_quit_menu_item_activated(self, menu_item):
625
        """
626
        Quits the application.
627
        """
628
        print menu_item, "chosen"
629
        self._confirm_and_quit()
630
    
631
    def on_help_menu_item_activated(self, menu_item):
632
        """
633
        Opens a web browser to the scenic web site.
634
        """
635
        print menu_item, "chosen"
636
        url = "http://scenic.sat.qc.ca"
637
        webbrowser.open(url)
638

    
639
    def on_save_menu_item_activated(self, menu_item):
640
        """
641
        Saves the addressbook and settings.
642
        """
643
        print menu_item, "chosen"
644
        print("-- Saving addressbook and configuration. -- ")
645
        self.save_configuration()
646

    
647
    # ---------------------- invitation dialogs -------------------
648

    
649
    def on_invite_contact_clicked(self, *args):
650
        """
651
        Sends an INVITE to the remote peer.
652
        """
653
        self.app.send_invite()
654
    
655
    def on_invite_contact_cancelled(self, *args):
656
        """
657
        Sends a CANCEL to the remote peer when invite contact window is closed.
658
        """
659
        # unschedule this timeout as we don't care if our peer answered or not
660
        self._unschedule_offerer_invite_timeout()
661
        self.app.send_cancel_and_disconnect()
662
        # don't let the delete-event propagate
663
        if self.calling_dialog.get_property('visible'):
664
            self.calling_dialog.hide()
665
        return True
666

    
667
    def show_error_dialog(self, text, callback=None):
668
        """
669
        Shows an error dialog, the old way.
670
        """
671
        # TODO: deprecate
672
        def _response_cb(widget, response_id, callback):
673
            widget.hide()
674
            if callback is not None:
675
                callback()
676
            widget.disconnect(slot1)
677

    
678
        self.error_label_widget.set_text(text)
679
        dialog = self.error_dialog
680
        dialog.set_modal(True)
681
        slot1 = dialog.connect('response', _response_cb, callback)
682
        dialog.show()
683
    
684
    def show_confirm_dialog(self, text, callback=None):
685
        """
686
        Shows a confirm dialog, the old way.
687
        """
688
        # TODO: deprecate
689
        def _response_cb(widget, response_id, callback):
690
            widget.hide()
691
            if callback is not None:
692
                callback(response_id == gtk.RESPONSE_OK)
693
            widget.disconnect(slot1)
694

    
695
        self.confirm_label.set_label(text)
696
        dialog = self.confirm_dialog
697
        dialog.set_modal(True)
698
        slot1 = dialog.connect('response', _response_cb, callback)
699
        dialog.show()
700

    
701
    def show_invited_dialog(self, text, callback=None):
702
        """ 
703
        We disconnect and reconnect the callbacks every time
704
        this is called, otherwise we'd would have multiple 
705
        callback invokations per response since the widget 
706
        stays alive 
707
        """
708
        def _response_cb(widget, response_id, callback):
709
            widget.hide()
710
            if callback is not None:
711
                callback(response_id)
712
            widget.disconnect(slot1)
713

    
714
        self.invited_dialog_label_widget.set_label(text)
715
        dialog = self.invited_dialog
716
        dialog.set_modal(True)
717
        slot1 = dialog.connect('response', _response_cb, callback)
718
        dialog.show()
719

    
720
    def hide_calling_dialog(self, msg="", err=""):
721
        """
722
        Hides the "calling_dialog" dialog.
723
        Shows an error dialog if the argument msg is set to "err", "timeout", "answTimeout", "send", "refuse" or "badAnsw".
724
        """
725
        self.calling_dialog.hide()
726
        text = None
727
        if msg == "err":
728
            text = _("Contact unreacheable.\n\nCould not connect to the IP address of this contact.")
729
        elif msg == "answTimeout":
730
            text = _("Contact answer timeout.\n\nThe contact did not answer soon enough.")
731
        elif msg == "send":
732
            text = _("Problem sending command.\n\nError: %s") % err
733
        elif msg == "refuse":
734
            text = _("Connection refused.\n\nThe contact refused the connection.")
735
        elif msg == "badAnsw":
736
            text = _("Invalid answer.\n\nThe answer was not valid.")
737
        if text is not None:
738
            self.show_error_dialog(text)
739

    
740
    def _unschedule_offerer_invite_timeout(self):
741
        """ Unschedules our offer invite timeout function """
742
        if self._offerer_invite_timeout is not None and self._offerer_invite_timeout.active():
743
            self._offerer_invite_timeout.cancel()
744
            self._offerer_invite_timeout = None
745
    
746
    def _schedule_offerer_invite_timeout(self):
747
        """ Schedules our offer invite timeout function """
748
        def _cl_offerer_invite_timed_out(self):
749
            # XXX
750
            # in case of invite timeout, act as if we'd cancelled the invite ourselves
751
            self.on_invite_contact_cancelled()
752
            if self.calling_dialog.get_property('visible'):
753
                self.hide_calling_dialog("answTimeout")
754
            # here we return false so that this callback is unregistered
755
            return False
756

    
757
        if self._offerer_invite_timeout is None or not self._offerer_invite_timeout.active():
758
            self._offerer_invite_timeout = reactor.callLater(5, _cl_offerer_invite_timed_out)
759
        else:
760
            print("Warning: Already scheduled a timeout as we're already inviting a contact")