Example #1
0
class MediaPlayerTest(EntertainerTest):
    """Test for entertainerlib.gui.widgets.media_player"""

    def setUp(self):
        '''Set up the test.'''
        EntertainerTest.setUp(self)

        self.player = MediaPlayer(clutter.Stage(), 100, 100)
        self.video_item = VideoItem()
        self.video_item.filename = THIS_DIR + '/data/VideoThumbnailer/test.avi'
        self.player.set_media(self.video_item)

    def test_create(self):
        '''Test correct MediaPlayer initialization.'''
        self.assertTrue(isinstance(self.player, MediaPlayer))

    def test_volume(self):
        '''Test the use of the volume property.'''
        self.player.volume = 10
        self.assertEqual(self.player.volume, 10)
        self.player.volume = 99
        self.assertEqual(self.player.volume, 20)
        self.player.volume = -10
        self.assertEqual(self.player.volume, 0)

    def test_volume_down(self):
        '''Test the use of the volume_down method.'''
        self.player.volume = 10
        self.player.volume_down()
        self.assertEqual(self.player.volume, 9)

    def test_volume_up(self):
        '''Test the use of the volume_up method.'''
        self.player.volume = 10
        self.player.volume_up()
        self.assertEqual(self.player.volume, 11)

    def test_set_media(self):
        '''Test the use of the set_media method.'''
        # The method is called during setUp.
        self.assertTrue(self.player.media is not None)

    def test_get_media(self):
        '''Test the use of the get_media method.'''
        self.assertEqual(self.player.get_media(), self.video_item)

    def test_has_media(self):
        '''Test the use of the has_media method.'''
        self.assertTrue(self.player.has_media())

    def test_getmediatype(self):
        '''Test the use of the get_media_type method.'''
        self.assertEqual(self.player.get_media_type(),
            self.video_item.get_type())

    def test_play_stop(self):
        '''Test the use of the play and stop methods.'''
        self.player.play()
        self.assertTrue(self.player.is_playing)
        self.player.stop()
        self.assertFalse(self.player.is_playing)

    def test_get_media_title(self):
        '''Test the use of the get_media_title method.'''
        self.assertEqual(self.player.get_media_title(),
            THIS_DIR + '/data/VideoThumbnailer/test.avi')

    def test_get_media_duration_string(self):
        '''Test the use of the get_media_title method.'''
        self.assertEqual(self.player.get_media_duration_string(), "00:00")
Example #2
0
class UserInterface:
    """A main GUI window of the Entertainer client."""

    def __init__(self, image_library, music_library, video_library, quit_client_callback):
        self.quit_client_callback = quit_client_callback
        self.config = Configuration()

        # Store the dimensions in case users want to return to window mode
        self.old_width = self.config.stage_width
        self.old_height = self.config.stage_height

        self.logger = Logger().getLogger("client.gui.UserInterface")

        self.window = gtk.Window()
        self.window.connect("destroy", self.destroy_callback)
        self.window.set_title("Entertainer")

        # Set the window icon
        icon_theme = gtk.icon_theme_get_default()
        try:
            icon = icon_theme.load_icon("entertainer", 48, 0)
            self.window.set_icon(icon)
        except gobject.GError:
            # Must not be installed from a package, get icon from the branch
            file_dir = os.path.dirname(__file__)
            icon_path = os.path.join(file_dir, "..", "..", "icons", "hicolor", "48x48", "apps", "entertainer.png")
            icon = gtk.gdk.pixbuf_new_from_file(icon_path)
            self.window.set_icon(icon)

        # cluttergtk.Embed contains the stage that is the canvas for the GUI
        embed = cluttergtk.Embed()
        # Enforce a minimum size to prevent weird widget bugs
        embed.set_size_request(self.config.stage_width, self.config.stage_height)
        self.window.add(embed)

        # The embed widget must be realized before you can get the stage.
        embed.realize()
        self.stage = embed.get_stage()

        self._hide_cursor_timeout_key = None

        self.stage.connect("key-press-event", self.handle_keyboard_event)
        self.stage.connect("motion-event", self._handle_motion_event)
        self.stage.set_color(self.config.theme.get_color("background"))
        self.stage.set_size(self.config.stage_width, self.config.stage_height)
        self.stage.set_title("Entertainer")

        if self.config.start_in_fullscreen:
            self._fullscreen()
            self.is_fullscreen = True
        else:
            self.is_fullscreen = False

        # Initialize Screen history (allows user to navigate "back")
        self.history = ScreenHistory(self._remove_from_stage)

        self.player = MediaPlayer(self.stage, self.config.stage_width, self.config.stage_height)
        self.player.connect("volume-changed", self._on_volume_changed)

        # Initialize menu overlay texture
        self.is_overlay = False
        self.menu_overlay = MenuOverlay(self.config.theme)
        self.menu_overlay.set_opacity(0)
        self.menu_overlay.set_size(self.config.stage_width, self.config.stage_height)
        self.stage.add(self.menu_overlay)

        self.volume_indicator = VolumeIndicator()
        self.stage.add(self.volume_indicator)
        self.volume_indicator.connect("hiding", self._on_volume_indicator_hiding)
        self.fade_screen_timeline = clutter.Timeline(200)
        alpha = clutter.Alpha(self.fade_screen_timeline, clutter.EASE_IN_OUT_SINE)
        self.fade_screen_behaviour = clutter.BehaviourOpacity(255, 0, alpha)

        # Transition object. Handles effects between screen changes.
        transition_factory = TransitionFactory(self._remove_from_stage)
        self.transition = transition_factory.generate_transition()

        # Screen factory to create new screens
        self.screen_factory = ScreenFactory(
            image_library,
            music_library,
            video_library,
            self.player,
            self.move_to_new_screen,
            self.move_to_previous_screen,
        )

        def default_key_to_user_event():
            """Return the default user event provided by an unmapped keyboard
            event."""
            return UserEvent.DEFAULT_EVENT

        # Dictionary for keyboard event handling
        self.key_to_user_event = defaultdict(
            default_key_to_user_event,
            {
                clutter.keysyms.Return: UserEvent.NAVIGATE_SELECT,
                clutter.keysyms.Up: UserEvent.NAVIGATE_UP,
                clutter.keysyms.Down: UserEvent.NAVIGATE_DOWN,
                clutter.keysyms.Left: UserEvent.NAVIGATE_LEFT,
                clutter.keysyms.Right: UserEvent.NAVIGATE_RIGHT,
                clutter.keysyms.BackSpace: UserEvent.NAVIGATE_BACK,
                clutter.keysyms.h: UserEvent.NAVIGATE_HOME,
                clutter.keysyms.w: UserEvent.NAVIGATE_FIRST_PAGE,
                clutter.keysyms.e: UserEvent.NAVIGATE_PREVIOUS_PAGE,
                clutter.keysyms.r: UserEvent.NAVIGATE_NEXT_PAGE,
                clutter.keysyms.t: UserEvent.NAVIGATE_LAST_PAGE,
                clutter.keysyms.f: UserEvent.TOGGLE_FULLSCREEN,
                clutter.keysyms.p: UserEvent.PLAYER_PLAY_PAUSE,
                clutter.keysyms.s: UserEvent.PLAYER_STOP,
                clutter.keysyms._1: UserEvent.USE_ASPECT_RATIO_1,
                clutter.keysyms._2: UserEvent.USE_ASPECT_RATIO_2,
                clutter.keysyms._3: UserEvent.USE_ASPECT_RATIO_3,
                clutter.keysyms._4: UserEvent.USE_ASPECT_RATIO_4,
                clutter.keysyms.x: UserEvent.PLAYER_SKIP_BACKWARD,
                clutter.keysyms.c: UserEvent.PLAYER_SKIP_FORWARD,
                clutter.keysyms.z: UserEvent.PLAYER_PREVIOUS,
                clutter.keysyms.v: UserEvent.PLAYER_NEXT,
                clutter.keysyms.m: UserEvent.PLAYER_VOLUME_UP,
                clutter.keysyms.l: UserEvent.PLAYER_VOLUME_DOWN,
                clutter.keysyms.q: UserEvent.QUIT,
                clutter.keysyms.Escape: UserEvent.QUIT,
            },
        )

        self.event_handlers = {
            UserEvent.DEFAULT_EVENT: self._handle_default,
            UserEvent.NAVIGATE_SELECT: self._handle_default,
            UserEvent.NAVIGATE_UP: self._handle_default,
            UserEvent.NAVIGATE_DOWN: self._handle_default,
            UserEvent.NAVIGATE_LEFT: self._handle_default,
            UserEvent.NAVIGATE_RIGHT: self._handle_default,
            UserEvent.NAVIGATE_BACK: self._handle_navigate_back,
            UserEvent.NAVIGATE_HOME: self._handle_navigate_home,
            UserEvent.NAVIGATE_FIRST_PAGE: self._handle_default,
            UserEvent.NAVIGATE_PREVIOUS_PAGE: self._handle_default,
            UserEvent.NAVIGATE_NEXT_PAGE: self._handle_default,
            UserEvent.NAVIGATE_LAST_PAGE: self._handle_default,
            UserEvent.TOGGLE_FULLSCREEN: self._handle_toggle_fullscreen,
            UserEvent.PLAYER_PLAY_PAUSE: self._handle_player_play_pause,
            UserEvent.PLAYER_STOP: self._handle_player_stop,
            UserEvent.USE_ASPECT_RATIO_1: self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_2: self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_3: self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_4: self._handle_aspect_ratio,
            UserEvent.PLAYER_SKIP_BACKWARD: self._handle_player_skip_backward,
            UserEvent.PLAYER_SKIP_FORWARD: self._handle_player_skip_forward,
            UserEvent.PLAYER_PREVIOUS: self._handle_player_previous,
            UserEvent.PLAYER_NEXT: self._handle_player_next,
            UserEvent.PLAYER_VOLUME_UP: self._handle_player_volume_up,
            UserEvent.PLAYER_VOLUME_DOWN: self._handle_player_volume_down,
            UserEvent.QUIT: self._handle_quit_client,
        }

        self.logger.debug("Frontend GUI initialized succesfully")

    def _fullscreen(self):
        """Set the window, stage, and config to fullscreen dimensions."""
        self.window.fullscreen()
        self.stage.set_fullscreen(True)
        self.config.stage_width = int(gtk.gdk.screen_width())
        self.config.stage_height = int(gtk.gdk.screen_height())

    def destroy_callback(self, widget):
        """Handle the GTK destroy signal and close gracefully."""
        self.shutdown()

    def confirm_exit(self):
        """Confirm that the user wants to shut down."""
        if self.current.name == "Question":
            # Confirmation dialog is already displayed.
            return

        kwargs = {
            "question": _("Are you sure you want to exit Entertainer?"),
            "answers": (_("Yes"), _("No")),
            "callbacks": (self.shutdown, None),
        }
        self.move_to_new_screen("question", kwargs)

    def start_up(self):
        """Start the user interface and make it visible."""
        self.show()
        self.stage.hide_cursor()
        self.current = self.create_screen("main")
        self.transition.forward_effect(None, self.current)
        self.enable_menu_overlay()

    def shutdown(self):
        """Shut down the user interface."""
        self.quit_client_callback()

    def _toggle_fullscreen(self):
        """Set the User Interface to fullscreen mode or back to window mode."""
        if self.is_fullscreen:
            self.stage.set_fullscreen(False)
            self.window.unfullscreen()
            self.config.stage_width = self.old_width
            self.config.stage_height = self.old_height
            self.is_fullscreen = False
        else:
            self._fullscreen()
            self.is_fullscreen = True

    def create_screen(self, screen_type, data=None):
        """Delegate to the screen factory to generate a screen."""
        screen = self.screen_factory.generate_screen(screen_type, data)
        self.stage.add(screen)
        return screen

    def move_to_new_screen(self, screen_type, kwargs=None, transition=Transition.FORWARD):
        """Callback method for screens and tabs to ask for new screens"""
        screen = self.create_screen(screen_type, kwargs)
        self.change_screen(screen, transition)

    def move_to_previous_screen(self):
        """Callback method to return to the previous screen in history."""
        screen = self.history.get_screen()
        screen.update()
        self.change_screen(screen, Transition.BACKWARD)

    def show(self):
        """Show the user interface."""
        self.window.show_all()

    def hide(self):
        """Hide the user interface."""
        self.window.hide_all()

    def _remove_from_stage(self, group):
        """Remove the listed group from the stage"""
        self.stage.remove(group)

    def enable_menu_overlay(self):
        """
        Enable menu overlay. Overlay should be enabled always when there is
        a video playing and menu showing at the same time. Overlay is not part
        of any specific screen. It is used for all screens when neccesary.
        """
        if not self.is_overlay:
            self.is_overlay = True
            self.menu_overlay.fade_in()
            self.player.is_reactive_allowed = False

    def disable_menu_overlay(self):
        """
        Disable menu overlay. Overlay should be disabled when current screen is
        a type of Screen.OSD.
        """
        if self.is_overlay:
            self.is_overlay = False
            self.menu_overlay.fade_out()
            self.player.is_reactive_allowed = True

    def change_screen(self, screen, direction):
        """Transition the given screen in the direction provided."""
        # Enable/Disable menu overlay
        if screen.kind == Screen.OSD:
            self.disable_menu_overlay()
        else:
            self.enable_menu_overlay()

        # Add current screen to screen history
        if direction == Transition.FORWARD:
            self.history.add_screen(self.current)

        # Change screen (Logical). Graphics is changed via animation
        from_screen = self.current
        self.current = screen

        # Animate screen change
        if direction == Transition.FORWARD:
            self.transition.forward_effect(from_screen, screen)
        elif direction == Transition.BACKWARD:
            self.transition.backward_effect(from_screen, screen)

    def _hide_cursor_timeout_callback(self):
        """Hide the cursor"""
        self.stage.hide_cursor()
        return True

    def _handle_motion_event(self, stage, clutter_event):
        """Show the cursor and start a timeout to hide it after 4 seconds."""
        self.stage.show_cursor()
        if self._hide_cursor_timeout_key is not None:
            gobject.source_remove(self._hide_cursor_timeout_key)
        self._hide_cursor_timeout_key = gobject.timeout_add(4000, self._hide_cursor_timeout_callback)

    def handle_keyboard_event(self, stage, clutter_event, event_handler=None):
        """Translate all received keyboard events to UserEvents."""
        if event_handler is None:
            event_handler = self.handle_user_event

        user_event = self.key_to_user_event[clutter_event.keyval]
        event_handler(UserEvent(user_event))

    def handle_user_event(self, event):
        """Delegate the user event to its proper handler method."""
        kind = event.get_type()
        self.event_handlers[kind](event)

    def _handle_aspect_ratio(self, event):
        """Handle UserEvent.USE_ASPECT_RATIO_*."""
        kind = event.get_type()

        set_methods = {
            UserEvent.USE_ASPECT_RATIO_1: self.player.set_native_ratio,
            UserEvent.USE_ASPECT_RATIO_2: self.player.set_widescreen_ratio,
            UserEvent.USE_ASPECT_RATIO_3: self.player.set_zoom_ratio,
            UserEvent.USE_ASPECT_RATIO_4: self.player.set_intelligent_ratio,
        }

        set_methods[kind]()
        self.current.handle_user_event(event)

    def _handle_default(self, event):
        """Handle the most basic case where the event is passed to the current
        screen."""
        self.current.handle_user_event(event)

    def _handle_navigate_back(self, event):
        """Handle UserEvent.NAVIGATE_BACK."""
        if not self.history.is_empty:
            self.move_to_previous_screen()

    def _handle_navigate_home(self, event):
        """Handle UserEvent.NAVIGATE_HOME."""
        self.move_to_new_screen("main")

    def _handle_player_next(self, event):
        """Handle UserEvent.PLAYER_NEXT."""
        self.player.next()

    def _handle_player_play_pause(self, event):
        """Handle UserEvent.PLAYER_PLAY_PAUSE."""
        if self.current.is_interested_in_play_action():
            self.current.execute_play_action()
        else:
            if self.player.is_playing:
                self.player.pause()
                self.current.handle_user_event(event)
            else:
                self.player.play()
                self.current.handle_user_event(event)

    def _handle_player_previous(self, event):
        """Handle UserEvent.PLAYER_PREVIOUS."""
        self.player.previous()

    def _handle_player_skip_backward(self, event):
        """Handle UserEvent.PLAYER_SKIP_BACKWARD."""
        self.player.skip_backward()
        self.current.handle_user_event(event)

    def _handle_player_skip_forward(self, event):
        """Handle UserEvent.PLAYER_SKIP_FORWARD."""
        self.player.skip_forward()
        self.current.handle_user_event(event)

    def _handle_player_stop(self, event):
        """Handle UserEvent.PLAYER_STOP."""
        if self.player.is_playing:
            self.player.stop()
            self.current.handle_user_event(event)

    def _handle_player_volume_up(self, event):
        """Handle UserEvent.PLAYER_VOLUME_UP."""
        self.player.volume_up()

    def _handle_player_volume_down(self, event):
        """Handle UserEvent.PLAYER_VOLUME_DOWN."""
        self.player.volume_down()

    def _handle_toggle_fullscreen(self, event):
        """Handle UserEvent.TOGGLE_FULLSCREEN."""
        self._toggle_fullscreen()

    def _handle_quit_client(self, event):
        """Handle UserEvent.QUIT."""
        self.confirm_exit()

    def _on_volume_changed(self, event):
        """Show volume indicator and fade out the screen (if needed)."""
        if not self.volume_indicator.visible:
            if not self.fade_screen_behaviour.is_applied(self.current):
                self.fade_screen_behaviour.apply(self.current)
            self.fade_screen_behaviour.set_bounds(255, 50)
            self.fade_screen_timeline.start()

        self.volume_indicator.show_volume(self.player.volume)

    def _on_volume_indicator_hiding(self, event):
        """Restore previous screen opacity."""
        self.fade_screen_behaviour.set_bounds(50, 255)
        self.fade_screen_timeline.start()
Example #3
0
class UserInterface:
    '''A main GUI window of the Entertainer client.'''

    def __init__(self, image_library, music_library, video_library,
        quit_client_callback):
        self.quit_client_callback = quit_client_callback
        self.config = Configuration()

        # Store the dimensions in case users want to return to window mode
        self.old_width = self.config.stage_width
        self.old_height = self.config.stage_height

        self.logger = Logger().getLogger('client.gui.UserInterface')

        self.window = gtk.Window()
        self.window.connect('destroy', self.destroy_callback)
        self.window.set_title('Entertainer')

        # Set the window icon
        icon_theme = gtk.icon_theme_get_default()
        try:
            icon = icon_theme.load_icon('entertainer', 48, 0)
            self.window.set_icon(icon)
        except gobject.GError:
            # Must not be installed from a package, get icon from the branch
            file_dir = os.path.dirname(__file__)
            icon_path = os.path.join(file_dir, '..', '..', 'icons',
                'hicolor', '48x48', 'apps', 'entertainer.png')
            icon = gtk.gdk.pixbuf_new_from_file(icon_path)
            self.window.set_icon(icon)

        # cluttergtk.Embed contains the stage that is the canvas for the GUI
        embed = cluttergtk.Embed()
        # Enforce a minimum size to prevent weird widget bugs
        embed.set_size_request(
            self.config.stage_width, self.config.stage_height)
        self.window.add(embed)

        # The embed widget must be realized before you can get the stage.
        embed.realize()
        self.stage = embed.get_stage()

        self._hide_cursor_timeout_key = None

        self.stage.connect('key-press-event', self.handle_keyboard_event)
        self.stage.connect('motion-event', self._handle_motion_event)
        self.stage.set_color(self.config.theme.get_color("background"))
        self.stage.set_size(self.config.stage_width, self.config.stage_height)
        self.stage.set_title("Entertainer")

        if self.config.start_in_fullscreen:
            self._fullscreen()
            self.is_fullscreen = True
        else:
            self.is_fullscreen = False

        # Initialize Screen history (allows user to navigate "back")
        self.history = ScreenHistory(self._remove_from_stage)

        self.player = MediaPlayer(self.stage,
            self.config.stage_width, self.config.stage_height)
        self.player.connect('volume-changed', self._on_volume_changed)

        # Initialize menu overlay texture
        self.is_overlay = False
        self.menu_overlay = MenuOverlay(self.config.theme)
        self.menu_overlay.set_opacity(0)
        self.menu_overlay.set_size(
            self.config.stage_width, self.config.stage_height)
        self.stage.add(self.menu_overlay)

        self.volume_indicator = VolumeIndicator()
        self.stage.add(self.volume_indicator)
        self.volume_indicator.connect('hiding',
            self._on_volume_indicator_hiding)
        self.fade_screen_timeline = clutter.Timeline(200)
        alpha = clutter.Alpha(self.fade_screen_timeline,
            clutter.EASE_IN_OUT_SINE)
        self.fade_screen_behaviour = clutter.BehaviourOpacity(255, 0, alpha)

        # Transition object. Handles effects between screen changes.
        transition_factory = TransitionFactory(self._remove_from_stage)
        self.transition = transition_factory.generate_transition()

        # Screen factory to create new screens
        self.screen_factory = ScreenFactory(
            image_library, music_library, video_library, self.player,
            self.move_to_new_screen, self.move_to_previous_screen)

        def default_key_to_user_event():
            '''Return the default user event provided by an unmapped keyboard
            event.'''
            return UserEvent.DEFAULT_EVENT

        # Dictionary for keyboard event handling
        self.key_to_user_event = defaultdict(default_key_to_user_event, {
            clutter.keysyms.Return : UserEvent.NAVIGATE_SELECT,
            clutter.keysyms.Up : UserEvent.NAVIGATE_UP,
            clutter.keysyms.Down : UserEvent.NAVIGATE_DOWN,
            clutter.keysyms.Left : UserEvent.NAVIGATE_LEFT,
            clutter.keysyms.Right : UserEvent.NAVIGATE_RIGHT,
            clutter.keysyms.BackSpace : UserEvent.NAVIGATE_BACK,
            clutter.keysyms.h : UserEvent.NAVIGATE_HOME,
            clutter.keysyms.w : UserEvent.NAVIGATE_FIRST_PAGE,
            clutter.keysyms.e : UserEvent.NAVIGATE_PREVIOUS_PAGE,
            clutter.keysyms.r : UserEvent.NAVIGATE_NEXT_PAGE,
            clutter.keysyms.t : UserEvent.NAVIGATE_LAST_PAGE,
            clutter.keysyms.f : UserEvent.TOGGLE_FULLSCREEN,
            clutter.keysyms.p : UserEvent.PLAYER_PLAY_PAUSE,
            clutter.keysyms.s : UserEvent.PLAYER_STOP,
            clutter.keysyms._1 : UserEvent.USE_ASPECT_RATIO_1,
            clutter.keysyms._2 : UserEvent.USE_ASPECT_RATIO_2,
            clutter.keysyms._3 : UserEvent.USE_ASPECT_RATIO_3,
            clutter.keysyms._4 : UserEvent.USE_ASPECT_RATIO_4,
            clutter.keysyms.x : UserEvent.PLAYER_SKIP_BACKWARD,
            clutter.keysyms.c : UserEvent.PLAYER_SKIP_FORWARD,
            clutter.keysyms.z : UserEvent.PLAYER_PREVIOUS,
            clutter.keysyms.v : UserEvent.PLAYER_NEXT,
            clutter.keysyms.m : UserEvent.PLAYER_VOLUME_UP,
            clutter.keysyms.l : UserEvent.PLAYER_VOLUME_DOWN,
            clutter.keysyms.q : UserEvent.QUIT,
            clutter.keysyms.Escape : UserEvent.QUIT
        })

        self.event_handlers = {
            UserEvent.DEFAULT_EVENT : self._handle_default,
            UserEvent.NAVIGATE_SELECT : self._handle_default,
            UserEvent.NAVIGATE_UP : self._handle_default,
            UserEvent.NAVIGATE_DOWN : self._handle_default,
            UserEvent.NAVIGATE_LEFT : self._handle_default,
            UserEvent.NAVIGATE_RIGHT : self._handle_default,
            UserEvent.NAVIGATE_BACK : self._handle_navigate_back,
            UserEvent.NAVIGATE_HOME : self._handle_navigate_home,
            UserEvent.NAVIGATE_FIRST_PAGE : self._handle_default,
            UserEvent.NAVIGATE_PREVIOUS_PAGE : self._handle_default,
            UserEvent.NAVIGATE_NEXT_PAGE : self._handle_default,
            UserEvent.NAVIGATE_LAST_PAGE : self._handle_default,
            UserEvent.TOGGLE_FULLSCREEN : self._handle_toggle_fullscreen,
            UserEvent.PLAYER_PLAY_PAUSE : self._handle_player_play_pause,
            UserEvent.PLAYER_STOP : self._handle_player_stop,
            UserEvent.USE_ASPECT_RATIO_1 : self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_2 : self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_3 : self._handle_aspect_ratio,
            UserEvent.USE_ASPECT_RATIO_4 : self._handle_aspect_ratio,
            UserEvent.PLAYER_SKIP_BACKWARD : self._handle_player_skip_backward,
            UserEvent.PLAYER_SKIP_FORWARD : self._handle_player_skip_forward,
            UserEvent.PLAYER_PREVIOUS : self._handle_player_previous,
            UserEvent.PLAYER_NEXT : self._handle_player_next,
            UserEvent.PLAYER_VOLUME_UP : self._handle_player_volume_up,
            UserEvent.PLAYER_VOLUME_DOWN : self._handle_player_volume_down,
            UserEvent.QUIT : self._handle_quit_client
        }

        self.logger.debug("Frontend GUI initialized succesfully")

    def _fullscreen(self):
        '''Set the window, stage, and config to fullscreen dimensions.'''
        self.window.fullscreen()
        self.stage.set_fullscreen(True)
        self.config.stage_width = int(gtk.gdk.screen_width())
        self.config.stage_height = int(gtk.gdk.screen_height())

    def destroy_callback(self, widget):
        '''Handle the GTK destroy signal and close gracefully.'''
        self.shutdown()

    def confirm_exit(self):
        '''Confirm that the user wants to shut down.'''
        if self.current.name == "Question":
            # Confirmation dialog is already displayed.
            return

        kwargs = {
            'question' : _('Are you sure you want to exit Entertainer?'),
            'answers' : (_('Yes'), _('No')),
            'callbacks' : (self.shutdown, None)
        }
        self.move_to_new_screen("question", kwargs)

    def start_up(self):
        '''Start the user interface and make it visible.'''
        self.show()
        self.stage.hide_cursor()
        self.current = self.create_screen("main")
        self.transition.forward_effect(None, self.current)
        self.enable_menu_overlay()

    def shutdown(self):
        '''Shut down the user interface.'''
        self.quit_client_callback()

    def _toggle_fullscreen(self):
        '''Set the User Interface to fullscreen mode or back to window mode.'''
        if self.is_fullscreen:
            self.stage.set_fullscreen(False)
            self.window.unfullscreen()
            self.config.stage_width = self.old_width
            self.config.stage_height = self.old_height
            self.is_fullscreen = False
        else:
            self._fullscreen()
            self.is_fullscreen = True

    def create_screen(self, screen_type, data=None):
        '''Delegate to the screen factory to generate a screen.'''
        screen = self.screen_factory.generate_screen(screen_type, data)
        self.stage.add(screen)
        return screen

    def move_to_new_screen(self, screen_type, kwargs=None,
        transition=Transition.FORWARD):
        '''Callback method for screens and tabs to ask for new screens'''
        screen = self.create_screen(screen_type, kwargs)
        self.change_screen(screen, transition)

    def move_to_previous_screen(self):
        '''Callback method to return to the previous screen in history.'''
        screen = self.history.get_screen()
        screen.update()
        self.change_screen(screen, Transition.BACKWARD)

    def show(self):
        '''Show the user interface.'''
        self.window.show_all()

    def hide(self):
        '''Hide the user interface.'''
        self.window.hide_all()

    def _remove_from_stage(self, group):
        '''Remove the listed group from the stage'''
        self.stage.remove(group)

    def enable_menu_overlay(self):
        """
        Enable menu overlay. Overlay should be enabled always when there is
        a video playing and menu showing at the same time. Overlay is not part
        of any specific screen. It is used for all screens when neccesary.
        """
        if not self.is_overlay:
            self.is_overlay = True
            self.menu_overlay.fade_in()
            self.player.is_reactive_allowed = False

    def disable_menu_overlay(self):
        """
        Disable menu overlay. Overlay should be disabled when current screen is
        a type of Screen.OSD.
        """
        if self.is_overlay:
            self.is_overlay = False
            self.menu_overlay.fade_out()
            self.player.is_reactive_allowed = True

    def change_screen(self, screen, direction):
        '''Transition the given screen in the direction provided.'''
        # Enable/Disable menu overlay
        if screen.kind == Screen.OSD:
            self.disable_menu_overlay()
        else:
            self.enable_menu_overlay()

        # Add current screen to screen history
        if direction == Transition.FORWARD:
            self.history.add_screen(self.current)

        # Change screen (Logical). Graphics is changed via animation
        from_screen = self.current
        self.current = screen

        # Animate screen change
        if direction == Transition.FORWARD:
            self.transition.forward_effect(from_screen, screen)
        elif direction == Transition.BACKWARD:
            self.transition.backward_effect(from_screen, screen)

    def _hide_cursor_timeout_callback(self):
        '''Hide the cursor'''
        self.stage.hide_cursor()
        return True

    def _handle_motion_event(self, stage, clutter_event):
        '''Show the cursor and start a timeout to hide it after 4 seconds.'''
        self.stage.show_cursor()
        if self._hide_cursor_timeout_key is not None:
            gobject.source_remove(self._hide_cursor_timeout_key)
        self._hide_cursor_timeout_key = gobject.timeout_add(4000,
            self._hide_cursor_timeout_callback)

    def handle_keyboard_event(self, stage, clutter_event, event_handler=None):
        '''Translate all received keyboard events to UserEvents.'''
        if event_handler is None:
            event_handler = self.handle_user_event

        user_event = self.key_to_user_event[clutter_event.keyval]
        event_handler(UserEvent(user_event))

    def handle_user_event(self, event):
        '''Delegate the user event to its proper handler method.'''
        kind = event.get_type()
        self.event_handlers[kind](event)

    def _handle_aspect_ratio(self, event):
        '''Handle UserEvent.USE_ASPECT_RATIO_*.'''
        kind = event.get_type()

        set_methods = {
            UserEvent.USE_ASPECT_RATIO_1 : self.player.set_native_ratio,
            UserEvent.USE_ASPECT_RATIO_2 : self.player.set_widescreen_ratio,
            UserEvent.USE_ASPECT_RATIO_3 : self.player.set_zoom_ratio,
            UserEvent.USE_ASPECT_RATIO_4 : self.player.set_intelligent_ratio
        }

        set_methods[kind]()
        self.current.handle_user_event(event)

    def _handle_default(self, event):
        '''Handle the most basic case where the event is passed to the current
        screen.'''
        self.current.handle_user_event(event)

    def _handle_navigate_back(self, event):
        '''Handle UserEvent.NAVIGATE_BACK.'''
        if not self.history.is_empty:
            self.move_to_previous_screen()

    def _handle_navigate_home(self, event):
        '''Handle UserEvent.NAVIGATE_HOME.'''
        self.move_to_new_screen('main')

    def _handle_player_next(self, event):
        '''Handle UserEvent.PLAYER_NEXT.'''
        self.player.next()

    def _handle_player_play_pause(self, event):
        '''Handle UserEvent.PLAYER_PLAY_PAUSE.'''
        if self.current.is_interested_in_play_action():
            self.current.execute_play_action()
        else:
            if self.player.is_playing:
                self.player.pause()
                self.current.handle_user_event(event)
            else:
                self.player.play()
                self.current.handle_user_event(event)

    def _handle_player_previous(self, event):
        '''Handle UserEvent.PLAYER_PREVIOUS.'''
        self.player.previous()

    def _handle_player_skip_backward(self, event):
        '''Handle UserEvent.PLAYER_SKIP_BACKWARD.'''
        self.player.skip_backward()
        self.current.handle_user_event(event)

    def _handle_player_skip_forward(self, event):
        '''Handle UserEvent.PLAYER_SKIP_FORWARD.'''
        self.player.skip_forward()
        self.current.handle_user_event(event)

    def _handle_player_stop(self, event):
        '''Handle UserEvent.PLAYER_STOP.'''
        if self.player.is_playing:
            self.player.stop()
            self.current.handle_user_event(event)

    def _handle_player_volume_up(self, event):
        '''Handle UserEvent.PLAYER_VOLUME_UP.'''
        self.player.volume_up()

    def _handle_player_volume_down(self, event):
        '''Handle UserEvent.PLAYER_VOLUME_DOWN.'''
        self.player.volume_down()

    def _handle_toggle_fullscreen(self, event):
        '''Handle UserEvent.TOGGLE_FULLSCREEN.'''
        self._toggle_fullscreen()

    def _handle_quit_client(self, event):
        '''Handle UserEvent.QUIT.'''
        self.confirm_exit()

    def _on_volume_changed(self, event):
        '''Show volume indicator and fade out the screen (if needed).'''
        if not self.volume_indicator.visible:
            if not self.fade_screen_behaviour.is_applied(self.current):
                self.fade_screen_behaviour.apply(self.current)
            self.fade_screen_behaviour.set_bounds(255, 50)
            self.fade_screen_timeline.start()

        self.volume_indicator.show_volume(self.player.volume)

    def _on_volume_indicator_hiding(self, event):
        '''Restore previous screen opacity.'''
        self.fade_screen_behaviour.set_bounds(50, 255)
        self.fade_screen_timeline.start()