Esempio n. 1
0
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)
        count = 0
        cursor = None

        Query()
        if Query.music_folder:
            try:
                cursor = tracker.query(Query.all_songs_count(), None)
                if cursor is not None and cursor.next(None):
                    count = cursor.get_integer(0)
            except Exception as e:
                logger.error("Tracker query crashed: %s", e)
                count = 0

            if count > 0:
                self._switch_to_player_view()
            # To revert to the No Music View when no songs are found
            else:
                if self.toolbar._selectionMode is False:
                    self._switch_to_empty_view()
        else:
            # Revert to No Music view if XDG dirs are not set
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()
Esempio n. 2
0
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        Query()
        if Query.music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed',
                             self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()
Esempio n. 3
0
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)
        count = 0
        cursor = None

        Query()
        if Query.music_folder:
            try:
                cursor = tracker.query(Query.all_songs_count(), None)
                if cursor is not None and cursor.next(None):
                    count = cursor.get_integer(0)
            except Exception as e:
                logger.error("Tracker query crashed: %s", e)
                count = 0

            if count > 0:
                self._switch_to_player_view()
            # To revert to the No Music View when no songs are found
            else:
                if self.toolbar._selectionMode is False:
                    self._switch_to_empty_view()
        else:
            # Revert to No Music view if XDG dirs are not set
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()
Esempio n. 4
0
    def __init__(self, application_id):
        super().__init__(application_id=application_id,
                         flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.props.resource_base_path = "/org/gnome/Music"
        GLib.set_application_name(_("Music"))
        GLib.set_prgname(application_id)
        GLib.setenv("PULSE_PROP_media.role", "music", True)

        self._init_style()
        self._window = None

        self._log = MusicLogger()
        self._search = Search()

        self._notificationmanager = NotificationManager(self)
        self._coreselection = CoreSelection()
        self._coremodel = CoreModel(self)
        # Order is important: CoreGrilo initializes the Grilo sources,
        # which in turn use CoreModel & CoreSelection extensively.
        self._coregrilo = CoreGrilo(self)

        self._settings = Gio.Settings.new('org.gnome.Music')
        self._lastfm_scrobbler = LastFmScrobbler(self)
        self._player = Player(self)

        InhibitSuspend(self)
        PauseOnSuspend(self._player)
Esempio n. 5
0
    def _setup_view(self):
        self._box = Gtk.VBox()
        self.player = Player()
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Stack(
            transition_type=StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True)
        if Gtk.get_minor_version() > 8:
            self.set_titlebar(self.toolbar.header_bar)
        else:
            self._box.pack_start(self.toolbar.header_bar, False, False, 0)
            self.set_hide_titlebar_when_maximized(True)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._stack, True, True, 0)
        self._box.pack_start(self.player.eventBox, False, False, 0)
        self._box.pack_start(self.selection_toolbar.eventbox, False, False, 0)
        self.add(self._box)
        count = 1
        cursor = tracker.query(Query.SONGS_COUNT, None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if count > 0:
            self.views.append(Views.Albums(self.toolbar, self.selection_toolbar, self.player))
            self.views.append(Views.Artists(self.toolbar, self.selection_toolbar, self.player))
            self.views.append(Views.Songs(self.toolbar, self.selection_toolbar, self.player))
            #self.views.append(Views.Playlist(self.toolbar, self.selection_toolbar, self.player))

            for i in self.views:
                self._stack.add_titled(i, i.title, i.title)

            self.toolbar.set_stack(self._stack)
            self.toolbar.searchbar.show()

            self._on_notify_model_id = self._stack.connect('notify::visible-child', self._on_notify_mode)
            self.connect('destroy', self._notify_mode_disconnect)
            self.connect('key_press_event', self._on_key_press)

            self.views[0].populate()
        #To revert to the No Music View when no songs are found
        else:
            self.views.append(Views.Empty(self.toolbar, self.player))
            self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)

        self.toolbar.set_state(ToolbarState.ALBUMS)
        self.toolbar.header_bar.show()
        self.player.eventBox.show_all()
        self._box.show()
        self.show()
Esempio n. 6
0
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        Query()
        if Query.music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()
Esempio n. 7
0
    def __init__(self, application_id):
        super().__init__(application_id=application_id,
                         flags=Gio.ApplicationFlags.FLAGS_NONE)

        self.props.resource_base_path = "/org/gnome/Music"
        GLib.set_application_name(_("Music"))
        GLib.set_prgname(application_id)
        GLib.setenv("PULSE_PROP_media.role", "music", True)

        self._init_style()
        self._window = None

        self._settings = Gio.Settings.new('org.gnome.Music')
        self._player = Player(self)

        InhibitSuspend(self)
        PauseOnSuspend(self._player)
Esempio n. 8
0
    def _setup_view(self):
        self._box = Gtk.VBox()
        self.player = Player()
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)
        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)
        count = 1
        cursor = tracker.query(Query.all_songs_count(), None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if count > 0:
            self._switch_to_player_view()
        # To revert to the No Music View when no songs are found
        else:
            if self.toolbar._selectionMode is False:
                self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()
Esempio n. 9
0
class Window(Gtk.ApplicationWindow):

    def __repr__(self):
        return '<Window>'

    @log
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self,
                                       application=app,
                                       title=_("Music"))
        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        selectAll = Gio.SimpleAction.new('selectAll', None)
        app.add_accelerator('<Primary>a', 'win.selectAll', None)
        selectAll.connect('activate', self._on_select_all)
        self.add_action(selectAll)
        selectNone = Gio.SimpleAction.new('selectNone', None)
        selectNone.connect('activate', self._on_select_none)
        self.add_action(selectNone)
        self.set_size_request(200, 100)
        self.set_icon_name('gnome-music')
        self.notification_handler = None
        self._loading_counter = 0

        self.prev_view = None
        self.curr_view = None

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self._setup_view()
        self._setup_loading_notification()
        self._setup_playlist_notification()

        self.window_size_update_timeout = None
        self.connect("window-state-event", self._on_window_state_event)
        self.connect("configure-event", self._on_configure_event)
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _setup_loading_notification(self):
        self._loading_notification = Gtk.Revealer(
                       halign=Gtk.Align.CENTER, valign=Gtk.Align.START)
        self._loading_notification.set_transition_type(
                                 Gtk.RevealerTransitionType.SLIDE_DOWN)
        self._overlay.add_overlay(self._loading_notification)

        def hide_notification_cb(button, self):
            self._loading_notification.set_reveal_child(False)

        grid = Gtk.Grid(margin_bottom=18, margin_start=18, margin_end=18)
        grid.set_column_spacing(18)
        grid.get_style_context().add_class('app-notification')

        spinner = Gtk.Spinner()
        spinner.start()
        grid.add(spinner)

        label = Gtk.Label.new(_("Loading"))
        grid.add(label)

        button = Gtk.Button.new_from_icon_name('window-close-symbolic',
                                               Gtk.IconSize.BUTTON)
        button.get_style_context().add_class('flat')
        button.connect('clicked', hide_notification_cb, self)
        grid.add(button)

        self._loading_notification.add(grid)
        self._loading_notification.show_all()

    @log
    def _setup_playlist_notification(self):
        self._playlist_notification_timeout_id = 0
        self._playlist_notification = Gtk.Revealer(halign=Gtk.Align.CENTER,
                                                   valign=Gtk.Align.START)
        self._playlist_notification.set_transition_type(
                       Gtk.RevealerTransitionType.SLIDE_DOWN)
        self._overlay.add_overlay(self._playlist_notification)

        grid = Gtk.Grid(margin_bottom=18, margin_start=18, margin_end=18)
        grid.set_column_spacing(12)
        grid.get_style_context().add_class('app-notification')

        def remove_notification_timeout(self):
            # Remove the timeout if any
            if self._playlist_notification_timeout_id > 0:
                GLib.source_remove(self._playlist_notification_timeout_id)
                self._playlist_notification_timeout_id = 0

        # Hide the notification and delete the playlist
        def hide_notification_cb(button, self):
            self._playlist_notification.set_reveal_child(False)

            if self.views[3].really_delete:
                playlist.delete_playlist(self.views[3].pl_todelete)
            else:
                self.views[3].really_delete = True

            remove_notification_timeout(self)

        # Undo playlist removal
        def undo_remove_cb(button, self):
            self.views[3].really_delete = False
            self._playlist_notification.set_reveal_child(False)
            self.views[3].undo_playlist_deletion()

            remove_notification_timeout(self)

        # Playlist name label
        self._playlist_notification.label = Gtk.Label('')
        grid.add(self._playlist_notification.label)

        # Undo button
        undo_button = Gtk.Button.new_with_mnemonic(_("_Undo"))
        undo_button.connect("clicked", undo_remove_cb, self)
        grid.add(undo_button)

        # Close button
        button = Gtk.Button.new_from_icon_name('window-close-symbolic',
                                               Gtk.IconSize.BUTTON)
        button.get_style_context().add_class('flat')
        button.connect('clicked', hide_notification_cb, self)
        grid.add(button)

        self._playlist_notification.add(grid)
        self._playlist_notification.show_all()

    @log
    def _on_changes_pending(self, data=None):
        def songs_available_cb(available):
            if available:
                if self.views[0] == self.views[-1]:
                    view = self.views.pop()
                    view.destroy()
                    self._switch_to_player_view()
                    self.toolbar._search_button.set_sensitive(True)
                    self.toolbar._select_button.set_sensitive(True)
                    self.toolbar.show_stack()
            elif (self.toolbar._selectionMode is False
                    and len(self.views) != 1):
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)
                view_count = len(self.views)
                for i in range(0, view_count):
                    view = self.views.pop()
                    view.destroy()
                self.toolbar.hide_stack()
                self._switch_to_empty_view()

        grilo.songs_available(songs_available_cb)

    @log
    def _on_configure_event(self, widget, event):
        if self.window_size_update_timeout is None:
            self.window_size_update_timeout = GLib.timeout_add(500, self.store_window_size_and_position, widget)

    @log
    def store_window_size_and_position(self, widget):
        size = widget.get_size()
        self.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))
        GLib.source_remove(self.window_size_update_timeout)
        self.window_size_update_timeout = None
        return False

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        self.proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
                                            Gio.DBusProxyFlags.NONE,
                                            None,
                                            'org.gnome.SettingsDaemon',
                                            '/org/gnome/SettingsDaemon/MediaKeys',
                                            'org.gnome.SettingsDaemon.MediaKeys',
                                            None)
        self.proxy.call_sync('GrabMediaPlayerKeys',
                             GLib.Variant('(su)', ('Music', 0)),
                             Gio.DBusCallFlags.NONE,
                             -1,
                             None)
        self.proxy.connect('g-signal', self._handle_media_keys)

    @log
    def _windows_focus_cb(self, window, event):
        try:
            self._grab_media_player_keys()
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        Query()
        if Query.music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        did_initial_state = self.settings.get_boolean('did-initial-state')
        view_class = None
        if did_initial_state:
            view_class = EmptyView
        else:
            view_class = InitialStateView
        self.views.append(view_class(self, self.player))

        self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self.settings.set_boolean('did-initial-state', True)
        self._on_notify_model_id = self._stack.connect('notify::visible-child', self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event', self._on_key_press)

        self.views.append(AlbumsView(self, self.player))
        self.views.append(ArtistsView(self, self.player))
        self.views.append(SongsView(self, self.player))
        self.views.append(PlaylistView(self, self.player))
        self.views.append(SearchView(self, self.player))
        self.views.append(EmptySearchView(self, self.player))

        for i in self.views:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()
        self.toolbar.dropdown.show()

        for i in self.views:
            GLib.idle_add(i.populate)

    @log
    def _on_select_all(self, action, param):
        if self.toolbar._selectionMode is False:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
        else:
            view = self._stack.get_visible_child().get_visible_child()

        view.select_all()

    @log
    def _on_select_none(self, action, param):
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
            view.unselect_all()
        else:
            view = self._stack.get_visible_child().get_visible_child()
            view.select_none()

    @log
    def show_playlist_notification(self):
        """Show a notification on playlist removal and provide an
        option to undo for 5 seconds.
        """

        # Callback to remove playlists
        def remove_playlist_timeout_cb(self):
            # Remove the playlist
            self.views[3].really_delete = False
            playlist.delete_playlist(self.views[3].pl_todelete)

            # Hide the notification
            self._playlist_notification.set_reveal_child(False)

            # Stop the timeout
            self._playlist_notification_timeout_id = 0

            return GLib.SOURCE_REMOVE

        # If a notification is already visible, remove that playlist
        if self._playlist_notification_timeout_id > 0:
            GLib.source_remove(self._playlist_notification_timeout_id)
            remove_playlist_timeout_cb(self)

        playlist_title = self.views[3].current_playlist.get_title()
        label = _("Playlist {} removed".format(playlist_title))

        self._playlist_notification.label.set_label(label)
        self._playlist_notification.set_reveal_child(True)

        timeout_id = GLib.timeout_add_seconds(5, remove_playlist_timeout_cb,
                                              self)
        self._playlist_notification_timeout_id = timeout_id

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        event_and_modifiers = (event.state & modifiers)

        if event_and_modifiers != 0:
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f and
                    event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.toolbar.searchbar.toggle_bar()
            # Play / Pause on Ctrl + SPACE
            if (event.keyval == Gdk.KEY_space
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                 self.player.play_pause()
            # Play previous on Ctrl + B
            if (event.keyval == Gdk.KEY_b
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.player.play_previous()
            # Play next on Ctrl + N
            if (event.keyval == Gdk.KEY_n
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.player.play_next()
            # Toggle repeat on Ctrl + R
            if (event.keyval == Gdk.KEY_r
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                if self.player.get_repeat_mode() == RepeatType.SONG:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SONG)
            # Toggle shuffle on Ctrl + S
            if (event.keyval == Gdk.KEY_s
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                if self.player.get_repeat_mode() == RepeatType.SHUFFLE:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SHUFFLE)
            # Go back from Album view on Alt + Left
            if (event.keyval == Gdk.KEY_Left and
                    event_and_modifiers == Gdk.ModifierType.MOD1_MASK):
                if (self.toolbar._state != ToolbarState.MAIN):
                    self.curr_view.set_visible_child(self.curr_view._grid)
                    self.toolbar.set_state(ToolbarState.MAIN)
            # Go to Albums view on Ctrl + 1
            if (event.keyval == Gdk.KEY_1
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 0)
            # Go to Artists view on Ctrl + 2
            if (event.keyval == Gdk.KEY_2
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 1)
            # Go to Songs view on Ctrl + 3
            if (event.keyval == Gdk.KEY_3
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 2)
            # Go to Playlists view on Ctrl + 4
            if (event.keyval == Gdk.KEY_4
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 3)
        else:
            if (event.keyval == Gdk.KEY_Delete):
                if self._stack.get_visible_child() == self.views[3]:
                    self.views[3].remove_playlist()
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.show_bar(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open the search bar when typing printable chars.
        key_unic = Gdk.keyval_to_unicode(event.keyval)
        if ((not self.toolbar.searchbar.get_search_mode()
                and not event.keyval == Gdk.KEY_space)
                and GLib.unichar_isprint(chr(key_unic))
                and (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK
                    or event_and_modifiers == 0)):
            self.toolbar.searchbar.show_bar(True)

    @log
    def _notify_mode_disconnect(self, data=None):
        self.player.Stop()
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        # Switch to all albums view when we're clicking Albums
        if self.curr_view == self.views[0] and not (self.prev_view == self.views[4] or self.prev_view == self.views[5]):
            self.curr_view.set_visible_child(self.curr_view._grid)

        # Slide out sidebar on switching to Artists or Playlists view
        if self.curr_view == self.views[1] or \
           self.curr_view == self.views[3]:
            self.curr_view.stack.set_visible_child_name('dummy')
            self.curr_view.stack.set_visible_child_name('sidebar')
        if self.curr_view != self.views[4] and self.curr_view != self.views[5]:
            self.toolbar.searchbar.show_bar(False)

        # Toggle the selection button for the EmptySearch view
        if self.curr_view == self.views[5] or \
           self.prev_view == self.views[5]:
            self.toolbar._select_button.set_sensitive(
                not self.toolbar._select_button.get_sensitive())

    @log
    def _toggle_view(self, btn, i):
        self._stack.set_visible_child(self.views[i])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.show_bar(button.get_active(),
                                        self.curr_view != self.views[4])
        if (not button.get_active() and
                (self.curr_view == self.views[4] or self.curr_view == self.views[5])):
            if self.toolbar._state == ToolbarState.MAIN:
                # We should get back to the view before the search
                self._stack.set_visible_child(self.views[4].previous_view)
            elif (self.views[4].previous_view == self.views[0] and
                 self.curr_view.get_visible_child() != self.curr_view._albumWidget and
                 self.curr_view.get_visible_child() != self.curr_view._artistAlbumsWidget):
                self._stack.set_visible_child(self.views[0])

            if self.toolbar._selectionMode:
                self.toolbar.set_selection_mode(False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode is False:
            self._on_changes_pending()
        else:
            in_playlist = self._stack.get_visible_child() == self.views[3]
            self.selection_toolbar._add_to_playlist_button.set_visible(not in_playlist)
            self.selection_toolbar._remove_from_playlist_button.set_visible(in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            add_to_playlist = PlaylistDialog(self)
            if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
                playlist.add_to_playlist(
                    add_to_playlist.get_selected(),
                    selected_tracks)
            self.toolbar.set_selection_mode(False)
            add_to_playlist.dialog_box.destroy()

        self._stack.get_visible_child().get_selected_tracks(callback)

    @log
    def _on_remove_from_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() != self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            playlist.remove_from_playlist(
                self.views[3].current_playlist,
                selected_tracks)
            self.toolbar.set_selection_mode(False)

        self._stack.get_visible_child().get_selected_tracks(callback)

    @log
    def push_loading_notification(self):
        """ Increases the counter of loading notification triggers
        running. If there is no notification is visible, the loading
        notification is started.
        """
        def show_notification_cb(self):
            self._loading_notification.set_reveal_child(True)
            self._show_notification_timeout_id = 0
            return GLib.SOURCE_REMOVE

        if self._loading_counter == 0:
            # Only show the notification after a small delay, thus
            # add a timeout. 500ms feels good enough.
            self._show_notification_timeout_id = GLib.timeout_add(
                    500, show_notification_cb, self)

        self._loading_counter = self._loading_counter + 1

    @log
    def pop_loading_notification(self):
        """ Decreases the counter of loading notification triggers
        running. If it reaches zero, the notification is withdrawn.
        """
        self._loading_counter = self._loading_counter - 1

        if self._loading_counter == 0:
            # Remove the previously set timeout, if any
            if self._show_notification_timeout_id > 0:
                GLib.source_remove(self._show_notification_timeout_id)
                self._show_notification_timeout_id = 0

            self._loading_notification.set_reveal_child(False)
Esempio n. 10
0
class Window(Gtk.ApplicationWindow):

    @log
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self,
                                       application=app,
                                       title=_("Music"))
        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        selectAll = Gio.SimpleAction.new('selectAll', None)
        app.add_accelerator('<Primary>a', 'win.selectAll', None)
        selectAll.connect('activate', self._on_select_all)
        self.add_action(selectAll)
        selectNone = Gio.SimpleAction.new('selectNone', None)
        selectNone.connect('activate', self._on_select_none)
        self.add_action(selectNone)
        self.set_size_request(200, 100)
        self.set_icon_name('gnome-music')

        self.prev_view = None
        self.curr_view = None

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self._setup_view()

        self.connect("window-state-event", self._on_window_state_event)
        self.configure_event_handler = self.connect("configure-event", self._on_configure_event)

        self.proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
                                            Gio.DBusProxyFlags.NONE,
                                            None,
                                            'org.gnome.SettingsDaemon',
                                            '/org/gnome/SettingsDaemon/MediaKeys',
                                            'org.gnome.SettingsDaemon.MediaKeys',
                                            None)
        self._grab_media_player_keys()
        try:
            self.proxy.connect('g-signal', self._handle_media_keys)
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _on_changes_pending(self, data=None):
        count = 1
        cursor = tracker.query(Query.all_songs_count(), None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if not count > 0:
            if self.toolbar._selectionMode is False and len(self.views) != 1:
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)
                view_count = len(self.views)
                for i in range(0, view_count):
                    view = self.views.pop()
                    view.destroy()
                self.toolbar.hide_stack()
                self._switch_to_empty_view()
        else:
            if (self.views[0] == self.views[-1]):
                view = self.views.pop()
                view.destroy()
                self._switch_to_player_view()
                self.toolbar._search_button.set_sensitive(True)
                self.toolbar._select_button.set_sensitive(True)
                self.toolbar.show_stack()

    def _on_configure_event(self, widget, event):
        with self.handler_block(self.configure_event_handler):
            GLib.idle_add(self.store_window_size_and_position, widget, priority=GLib.PRIORITY_LOW)

    @log
    def store_window_size_and_position(self, widget):
        size = widget.get_size()
        self.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        try:
            self.proxy.call_sync('GrabMediaPlayerKeys',
                                 GLib.Variant('(su)', ('Music', 0)),
                                 Gio.DBusCallFlags.NONE,
                                 -1,
                                 None)
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _windows_focus_cb(self, window, event):
        self._grab_media_player_keys()

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)
        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)
        count = 0
        cursor = None

        if Query.music_folder and Query.download_folder:
            try:
                cursor = tracker.query(Query.all_songs_count(), None)
                if cursor is not None and cursor.next(None):
                    count = cursor.get_integer(0)
            except Exception as e:
                logger.error("Tracker query crashed: %s" % e)
                count = 0

            if count > 0:
                self._switch_to_player_view()
            # To revert to the No Music View when no songs are found
            else:
                if self.toolbar._selectionMode is False:
                    self._switch_to_empty_view()
        else:
            # Revert to No Music view if XDG dirs are not set
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        self.views.append(Views.Empty(self, self.player))
        self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self._on_notify_model_id = self._stack.connect('notify::visible-child', self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event', self._on_key_press)

        self.views.append(Views.Albums(self, self.player))
        self.views.append(Views.Artists(self, self.player))
        self.views.append(Views.Songs(self, self.player))
        self.views.append(Views.Playlist(self, self.player))
        self.views.append(Views.Search(self, self.player))

        for i in self.views:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()
        self.toolbar.dropdown.show()

        self.views[0].populate()

    @log
    def _set_selection(self, model, value, parent=None):
        count = 0
        _iter = model.iter_children(parent)
        while _iter is not None:
            if model.iter_has_child(_iter):
                count += self._set_selection(model, value, _iter)
            if model[_iter][5]:
                model.set(_iter, [6], [value])
                count += 1
            _iter = model.iter_next(_iter)
        return count

    @log
    def _on_select_all(self, action, param):
        if self.toolbar._selectionMode is False:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            model = self._stack.get_visible_child().model
        else:
            model = self._stack.get_visible_child().get_visible_child().model
        count = self._set_selection(model, True)
        if count > 0:
            self.toolbar._selection_menu_label.set_text(
                ngettext("Selected %d item", "Selected %d items", count) % count)
            self.selection_toolbar._add_to_playlist_button.set_sensitive(True)
            self.selection_toolbar._remove_from_playlist_button.set_sensitive(True)
        elif count == 0:
            self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    @log
    def _on_select_none(self, action, param):
        if self.toolbar._state == ToolbarState.MAIN:
            model = self._stack.get_visible_child().model
        else:
            model = self._stack.get_visible_child().get_visible_child().model
        self._set_selection(model, False)
        self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
        self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
        self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    @log
    def _init_loading_notification(self):
        self.notification = Gd.Notification()
        self.notification.set_timeout(5)
        grid = Gtk.Grid(valign=Gtk.Align.CENTER, margin_right=8)
        grid.set_column_spacing(8)
        spinner = Gtk.Spinner()
        spinner.start()
        grid.add(spinner)
        label = Gtk.Label.new(_("Loading"))
        grid.add(label)
        self.notification.add(grid)
        self.notification.show_all()
        GLib.timeout_add(1000, self._overlay.add_overlay, self.notification)

    @log
    def _init_playlist_removal_notification(self):
        self.notification = Gd.Notification()
        self.notification.set_timeout(20)

        grid = Gtk.Grid(valign=Gtk.Align.CENTER, margin_right=8)
        grid.set_column_spacing(8)
        self.notification.add(grid)

        undo_button = Gtk.Button.new_with_mnemonic(_("_Undo"))
        label = _("Playlist %s removed" % (
            self.views[3].current_playlist.get_title()))
        grid.add(Gtk.Label.new(label))
        grid.add(undo_button)

        self.notification.show_all()
        self._overlay.add_overlay(self.notification)

        self.notification.deletion_index = self.views[3].current_playlist_index

        self.notification.connect("dismissed", self._playlist_removal_notification_dismissed)
        undo_button.connect("clicked", self._undo_deletion)

    @log
    def _playlist_removal_notification_dismissed(self, widget):
        if self.views[3].really_delete:
            Views.playlists.delete_playlist(self.views[3].pl_todelete)
        else:
            self.views[3].really_delete = True

    @log
    def _undo_deletion(self, widget):
        self.views[3].really_delete = False
        self.notification.dismiss()
        self.views[3].undo_playlist_deletion(self.notification.deletion_index)

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        event_and_modifiers = (event.state & modifiers)

        if event_and_modifiers != 0:
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f and
                    event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.toolbar.searchbar.toggle_bar()
            # Go back from Album view on Alt + Left
            if (event.keyval == Gdk.KEY_Left and
                    event_and_modifiers == Gdk.ModifierType.MOD1_MASK):
                if (self.toolbar._state != ToolbarState.MAIN):
                    self.curr_view.set_visible_child(self.curr_view._grid)
                    self.toolbar.set_state(ToolbarState.MAIN)
        else:
            if (event.keyval == Gdk.KEY_Delete):
                if self._stack.get_visible_child() == self.views[3]:
                    self.views[3]._on_delete_activate(None)
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.show_bar(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open search bar when typing printable chars if it not opened
        # Make sure we skip unprintable chars and don't grab space press
        # (this is used for play/pause)
        if not self.toolbar.searchbar.get_reveal_child() and not event.keyval == Gdk.KEY_space:
            if (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK or
                    event_and_modifiers == 0) and \
                    GLib.unichar_isprint(chr(Gdk.keyval_to_unicode(event.keyval))):
                self.toolbar.searchbar.show_bar(True)
        else:
            if not self.toolbar.searchbar.get_reveal_child():
                if event.keyval == Gdk.KEY_space and self.player.actionbar.get_visible():
                    if self.get_focus() != self.player.playBtn:
                        self.player.play_pause()

    @log
    def _notify_mode_disconnect(self, data=None):
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        # Switch to all albums view when we're clicking Albums
        if self.curr_view == self.views[0]:
            self.curr_view.set_visible_child(self.curr_view._grid)

        # Slide out sidebar on switching to Artists or Playlists view
        if self.curr_view == self.views[1] or \
           self.curr_view == self.views[3]:
            self.curr_view.stack.set_visible_child_name('dummy')
            self.curr_view.stack.set_visible_child_name('sidebar')
        if self.curr_view != self.views[4]:
            self.toolbar.searchbar.show_bar(False)

    @log
    def _toggle_view(self, btn, i):
        self._stack.set_visible_child(self.views[i])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.show_bar(button.get_active(),
                                        self.curr_view != self.views[4])
        if not button.get_active() and self.curr_view == self.views[4] and \
           self.toolbar._state == ToolbarState.MAIN:
            self._stack.set_visible_child(self.prev_view)
            if self.toolbar._selectionMode:
                self.toolbar.set_selection_mode(False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode is False:
            self._on_changes_pending()
        else:
            in_playlist = self._stack.get_visible_child() == self.views[3]
            self.selection_toolbar._add_to_playlist_button.set_visible(not in_playlist)
            self.selection_toolbar._remove_from_playlist_button.set_visible(in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            add_to_playlist = Widgets.PlaylistDialog(self)
            if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
                playlist.add_to_playlist(
                    add_to_playlist.get_selected(),
                    selected_tracks)
            self.toolbar.set_selection_mode(False)
            add_to_playlist.dialog_box.destroy()

        self._stack.get_visible_child().get_selected_tracks(callback)

    @log
    def _on_remove_from_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() != self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            playlist.remove_from_playlist(
                self.views[3].current_playlist,
                selected_tracks)
            self.toolbar.set_selection_mode(False)

        self._stack.get_visible_child().get_selected_tracks(callback)
Esempio n. 11
0
class Window(Gtk.ApplicationWindow):

    selected_items_count = GObject.Property(type=int, default=0, minimum=0)
    selection_mode = GObject.Property(type=bool, default=False)

    def __repr__(self):
        return '<Window>'

    @log
    def __init__(self, app):
        super().__init__(application=app, title=_("Music"))

        self._settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self._settings.create_action('repeat'))
        select_all = Gio.SimpleAction.new('select_all', None)
        select_all.connect('activate', self._select_all)
        self.add_action(select_all)
        select_none = Gio.SimpleAction.new('select_none', None)
        select_none.connect('activate', self._select_none)
        self.add_action(select_none)

        self.set_size_request(200, 100)
        WindowPlacement(self, self._settings)

        self.prev_view = None
        self.curr_view = None

        self.notifications_popup = NotificationsPopup()
        self._setup_view()

        MediaKeys(self._player, self)

        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _on_changes_pending(self, data=None):
        # FIXME: This is not working right.
        def songs_available_cb(available):
            view_count = len(self.views)
            if (available
                    and view_count == 1):
                self._switch_to_player_view()
            elif (not available
                    and not self.props.selection_mode
                    and view_count != 1):
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)

                for i in range(View.ALBUM, view_count):
                    view = self.views.pop()
                    view.destroy()

                self._switch_to_empty_view()

        grilo.songs_available(songs_available_cb)

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self._headerbar = HeaderBar()
        self._searchbar = Searchbar()
        self._player = Player(self)
        self._player_toolbar = PlayerToolbar(self._player, self)
        selection_toolbar = SelectionToolbar()
        self.views = [None] * len(View)
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            homogeneous=False,
            visible=True,
            can_focus=False)

        self._headerbar.bind_property(
            "search-mode-enabled", self._searchbar, "search-mode-enabled",
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE)
        self._searchbar.props.stack = self._stack
        self._headerbar.connect(
            'back-button-clicked', self._switch_back_from_childview)

        self.connect('notify::selection-mode', self._on_selection_mode_changed)
        self.bind_property(
            'selected-items-count', self._headerbar, 'selected-items-count')
        self.bind_property(
            'selected-items-count', selection_toolbar, 'selected-items-count')
        self.bind_property(
            'selection-mode', self._headerbar, 'selection-mode',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE)
        self.bind_property(
            'selection-mode', selection_toolbar, 'visible',
            GObject.BindingFlags.SYNC_CREATE)
        # Create only the empty view at startup
        # if no music, switch to empty view and hide stack
        # if some music is available, populate stack with mainviews,
        # show stack and set empty_view to empty_search_view
        self.views[View.EMPTY] = EmptyView()
        self._stack.add_named(self.views[View.EMPTY], "emptyview")

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay()
        self._overlay.add(self._stack)
        # FIXME: Need to find a proper way to do this.
        self._overlay.add_overlay(self._searchbar._dropdown)
        self._overlay.add_overlay(self.notifications_popup)
        self.set_titlebar(self._headerbar)
        self._box.pack_start(self._searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self._player_toolbar, False, False, 0)
        self._box.pack_start(selection_toolbar, False, False, 0)
        self.add(self._box)

        self._headerbar._search_button.connect(
            'toggled', self._on_search_toggled)
        selection_toolbar.connect(
            'add-to-playlist', self._on_add_to_playlist)

        self._headerbar.props.state = HeaderBar.State.MAIN
        self._headerbar.show()
        self._overlay.show()
        self._player_toolbar.show_all()
        self._box.show()
        self.show()

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        if Query().music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

    @log
    def _switch_to_empty_view(self):
        did_initial_state = self._settings.get_boolean('did-initial-state')

        if not grilo.props.tracker_available:
            self.views[View.EMPTY].props.state = EmptyView.State.NO_TRACKER
        elif did_initial_state:
            self.views[View.EMPTY].props.state = EmptyView.State.EMPTY
        else:
            self.views[View.EMPTY].props.state = EmptyView.State.INITIAL

        self._headerbar.props.state = HeaderBar.State.EMPTY

    @log
    def _switch_to_player_view(self):
        self._settings.set_boolean('did-initial-state', True)
        self._on_notify_model_id = self._stack.connect(
            'notify::visible-child', self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect(
            'key_press_event', self._on_key_press)

        self._btn_ctrl = Gtk.GestureMultiPress().new(self)
        self._btn_ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
        # Mouse button 8 is the back button.
        self._btn_ctrl.props.button = 8
        self._btn_ctrl.connect("pressed", self._on_back_button_pressed)

        # FIXME: In case Grilo is already initialized before the views
        # get created, they never receive a 'ready' signal to trigger
        # population. To fix this another check was added to baseview
        # to populate if grilo is ready at the end of init. For this to
        # work however, the headerbar stack needs to be created and
        # populated. This is done below, by binding headerbar.stack to
        # to window._stack. For this to succeed, the stack needs to be
        # filled with something: Gtk.Box.
        # This is a bit of circular logic that needs to be fixed.
        self.views[View.ALBUM] = Gtk.Box()
        self.views[View.ARTIST] = Gtk.Box()
        self.views[View.SONG] = Gtk.Box()
        self.views[View.PLAYLIST] = Gtk.Box()
        self.views[View.SEARCH] = Gtk.Box()

        self.views[View.EMPTY].props.state = EmptyView.State.SEARCH
        self._headerbar.props.state = HeaderBar.State.MAIN
        self._headerbar.props.stack = self._stack
        self._searchbar.show()

        self.views[View.ALBUM] = AlbumsView(self, self._player)
        self.views[View.ARTIST] = ArtistsView(self, self._player)
        self.views[View.SONG] = SongsView(self, self._player)
        self.views[View.PLAYLIST] = PlaylistView(self, self._player)
        self.views[View.SEARCH] = SearchView(self, self._player)

        selectable_views = [View.ALBUM, View.ARTIST, View.SONG, View.SEARCH]
        for view in selectable_views:
            self.views[view].bind_property(
                'selected-items-count', self, 'selected-items-count')

        # empty view has already been created in self._setup_view starting at
        # View.ALBUM
        # empty view state is changed once album view is visible to prevent it
        # from being displayed during startup
        for i in self.views[View.ALBUM:]:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self._stack.set_visible_child(self.views[View.ALBUM])

        self.views[View.SEARCH].bind_property(
            'search-state', self._searchbar, 'search-state')

    @log
    def _select_all(self, action=None, param=None):
        if not self.props.selection_mode:
            return
        if self._headerbar.props.state == HeaderBar.State.MAIN:
            view = self._stack.get_visible_child()
        else:
            view = self._stack.get_visible_child().get_visible_child()

        view.select_all()

    @log
    def _select_none(self, action=None, param=None):
        if not self.props.selection_mode:
            return
        if self._headerbar.props.state == HeaderBar.State.MAIN:
            view = self._stack.get_visible_child()
            view.unselect_all()
        else:
            view = self._stack.get_visible_child().get_visible_child()
            view.select_none()

    @log
    def _on_key_press(self, widget, event):
        modifiers = event.get_state() & Gtk.accelerator_get_default_mod_mask()
        (_, keyval) = event.get_keyval()

        control_mask = Gdk.ModifierType.CONTROL_MASK
        shift_mask = Gdk.ModifierType.SHIFT_MASK
        mod1_mask = Gdk.ModifierType.MOD1_MASK
        shift_ctrl_mask = control_mask | shift_mask

        # Ctrl+<KEY>
        if control_mask == modifiers:
            if keyval == Gdk.KEY_a:
                self._select_all()
            # Open search bar on Ctrl + F
            if (keyval == Gdk.KEY_f
                    and not self.views[View.PLAYLIST].rename_active
                    and self._headerbar.props.state != HeaderBar.State.SEARCH):
                self._searchbar.toggle()
            # Play / Pause on Ctrl + SPACE
            if keyval == Gdk.KEY_space:
                self._player.play_pause()
            # Play previous on Ctrl + B
            if keyval == Gdk.KEY_b:
                self._player.previous()
            # Play next on Ctrl + N
            if keyval == Gdk.KEY_n:
                self._player.next()
            if keyval == Gdk.KEY_q:
                self.props.application.quit()
            # Toggle repeat on Ctrl + R
            if keyval == Gdk.KEY_r:
                if self._player.props.repeat_mode == RepeatMode.SONG:
                    self._player.props.repeat_mode = RepeatMode.NONE
                    repeat_state = GLib.Variant("s", ("none"))
                else:
                    self._player.props.repeat_mode = RepeatMode.SONG
                    repeat_state = GLib.Variant("s", ("song"))
                self.lookup_action('repeat').change_state(repeat_state)
            # Toggle shuffle on Ctrl + S
            if keyval == Gdk.KEY_s:
                if self._player.props.repeat_mode == RepeatMode.SHUFFLE:
                    self._player.props.repeat_mode = RepeatMode.NONE
                    repeat_state = GLib.Variant("s", ("none"))
                else:
                    self._player.props.repeat_mode = RepeatMode.SHUFFLE
                    repeat_state = GLib.Variant("s", ("shuffle"))
                self.lookup_action('repeat').change_state(repeat_state)
        # Ctrl+Shift+<KEY>
        elif modifiers == shift_ctrl_mask:
            if keyval == Gdk.KEY_A:
                self._select_none()
        # Alt+<KEY>
        elif modifiers == mod1_mask:
            # Go back from child view on Alt + Left
            if keyval == Gdk.KEY_Left:
                self._switch_back_from_childview()
            # Headerbar switching
            if keyval in [Gdk.KEY_1, Gdk.KEY_KP_1]:
                self._toggle_view(View.ALBUM)
            if keyval in [Gdk.KEY_2, Gdk.KEY_KP_2]:
                self._toggle_view(View.ARTIST)
            if keyval in [Gdk.KEY_3, Gdk.KEY_KP_3]:
                self._toggle_view(View.SONG)
            if keyval in [Gdk.KEY_4, Gdk.KEY_KP_4]:
                self._toggle_view(View.PLAYLIST)
        # No modifier
        else:
            if (keyval == Gdk.KEY_AudioPlay
                    or keyval == Gdk.KEY_AudioPause):
                self._player.play_pause()

            if keyval == Gdk.KEY_AudioStop:
                self._player.stop()

            if keyval == Gdk.KEY_AudioPrev:
                self._player.previous()

            if keyval == Gdk.KEY_AudioNext:
                self._player.next()

            child = self._stack.get_visible_child()
            if (keyval == Gdk.KEY_Delete
                    and child == self.views[View.PLAYLIST]):
                self.views[View.PLAYLIST].remove_playlist()
            # Close selection mode or search bar after Esc is pressed
            if keyval == Gdk.KEY_Escape:
                if self.props.selection_mode:
                    self.props.selection_mode = False
                else:
                    self._searchbar.reveal(False)

        # Open the search bar when typing printable chars.
        key_unic = Gdk.keyval_to_unicode(keyval)
        if ((not self._searchbar.get_search_mode()
                and not keyval == Gdk.KEY_space)
                and GLib.unichar_isprint(chr(key_unic))
                and (modifiers == shift_mask
                     or modifiers == 0)
                and not self.views[View.PLAYLIST].rename_active
                and self._headerbar.props.state != HeaderBar.State.SEARCH):
            self._searchbar.reveal(True)

    @log
    def _on_back_button_pressed(self, gesture, n_press, x, y):
        self.headerbar._on_back_button_clicked()

    @log
    def _notify_mode_disconnect(self, data=None):
        self._player.stop()
        self.notifications_popup.terminate_pending()
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        if (self.curr_view != self.views[View.SEARCH]
                and self.curr_view != self.views[View.EMPTY]):
            self._searchbar.reveal(False)

        # Disable the selection button for the EmptySearch and Playlist
        # view
        no_selection_mode = [
            self.views[View.EMPTY],
            self.views[View.PLAYLIST]
        ]
        allowed = self.curr_view not in no_selection_mode
        self._headerbar.props.selection_mode_allowed = allowed

        # Disable renaming playlist if it was active when leaving
        # Playlist view
        if (self.prev_view == self.views[View.PLAYLIST]
                and self.views[View.PLAYLIST].rename_active):
            self.views[View.PLAYLIST].disable_rename_playlist()

    @log
    def _toggle_view(self, view_enum):
        # TODO: The SEARCH state actually refers to the child state of
        # the search mode. This fixes the behaviour as needed, but is
        # incorrect: searchview currently does not switch states
        # correctly.
        if (not self.props.selection_mode
                and not self._headerbar.props.state == HeaderBar.State.CHILD
                and not self._headerbar.props.state == HeaderBar.State.SEARCH):
            self._stack.set_visible_child(self.views[view_enum])

    @log
    def _on_search_toggled(self, button, data=None):
        self._searchbar.reveal(
            button.get_active(), self.curr_view != self.views[View.SEARCH])
        if (not button.get_active()
                and (self.curr_view == self.views[View.SEARCH]
                    or self.curr_view == self.views[View.EMPTY])):
            child = self.curr_view.get_visible_child()
            if self._headerbar.props.state == HeaderBar.State.MAIN:
                # We should get back to the view before the search
                self._stack.set_visible_child(
                    self.views[View.SEARCH].previous_view)
            elif (self.views[View.SEARCH].previous_view == self.views[View.ALBUM]
                    and child != self.curr_view._album_widget
                    and child != self.curr_view._artist_albums_widget):
                self._stack.set_visible_child(self.views[View.ALBUM])

            if self.props.selection_mode:
                self.props.selection_mode = False

    @log
    def _switch_back_from_childview(self, klass=None):
        if self.props.selection_mode:
            return

        views_with_child = [
            self.views[View.ALBUM],
            self.views[View.SEARCH]
        ]
        if self.curr_view in views_with_child:
            self.curr_view._back_button_clicked(self.curr_view)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.props.selection_mode:
            self._player_toolbar.hide()
        elif self._player.props.playing:
            self._player_toolbar.show()
        if not self.props.selection_mode:
            self._on_changes_pending()

    @log
    def _on_add_to_playlist(self, widget):
        if self._stack.get_visible_child() == self.views[View.PLAYLIST]:
            return

        def callback(selected_songs):
            if len(selected_songs) < 1:
                return

            playlist_dialog = PlaylistDialog(
                self, self.views[View.PLAYLIST].pls_todelete)
            if playlist_dialog.run() == Gtk.ResponseType.ACCEPT:
                playlists.add_to_playlist(
                    playlist_dialog.get_selected(), selected_songs)
            self.props.selection_mode = False
            playlist_dialog.destroy()

        self._stack.get_visible_child().get_selected_songs(callback)

    @log
    def set_player_visible(self, visible):
        """Set PlayWidget action visibility

        :param bool visible: actionbar visibility
        """
        self._player_toolbar.set_visible(visible)
Esempio n. 12
0
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self._headerbar = HeaderBar()
        self._searchbar = Searchbar()
        self._player = Player(self)
        self._player_toolbar = PlayerToolbar(self._player, self)
        selection_toolbar = SelectionToolbar()
        self.views = [None] * len(View)
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            homogeneous=False,
            visible=True,
            can_focus=False)

        self._headerbar.bind_property(
            "search-mode-enabled", self._searchbar, "search-mode-enabled",
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE)
        self._searchbar.props.stack = self._stack
        self._headerbar.connect(
            'back-button-clicked', self._switch_back_from_childview)

        self.connect('notify::selection-mode', self._on_selection_mode_changed)
        self.bind_property(
            'selected-items-count', self._headerbar, 'selected-items-count')
        self.bind_property(
            'selected-items-count', selection_toolbar, 'selected-items-count')
        self.bind_property(
            'selection-mode', self._headerbar, 'selection-mode',
            GObject.BindingFlags.BIDIRECTIONAL |
            GObject.BindingFlags.SYNC_CREATE)
        self.bind_property(
            'selection-mode', selection_toolbar, 'visible',
            GObject.BindingFlags.SYNC_CREATE)
        # Create only the empty view at startup
        # if no music, switch to empty view and hide stack
        # if some music is available, populate stack with mainviews,
        # show stack and set empty_view to empty_search_view
        self.views[View.EMPTY] = EmptyView()
        self._stack.add_named(self.views[View.EMPTY], "emptyview")

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay()
        self._overlay.add(self._stack)
        # FIXME: Need to find a proper way to do this.
        self._overlay.add_overlay(self._searchbar._dropdown)
        self._overlay.add_overlay(self.notifications_popup)
        self.set_titlebar(self._headerbar)
        self._box.pack_start(self._searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self._player_toolbar, False, False, 0)
        self._box.pack_start(selection_toolbar, False, False, 0)
        self.add(self._box)

        self._headerbar._search_button.connect(
            'toggled', self._on_search_toggled)
        selection_toolbar.connect(
            'add-to-playlist', self._on_add_to_playlist)

        self._headerbar.props.state = HeaderBar.State.MAIN
        self._headerbar.show()
        self._overlay.show()
        self._player_toolbar.show_all()
        self._box.show()
        self.show()

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        if Query().music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()
Esempio n. 13
0
class Window(Gtk.ApplicationWindow):
    def __repr__(self):
        return '<Window>'

    @log
    def __init__(self, app):
        super().__init__(application=app, title=_("Music"))

        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        self.set_size_request(200, 100)
        self.set_default_icon_name('gnome-music')

        self.prev_view = None
        self.curr_view = None

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(
                size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self._setup_view()
        self.notifications_popup = NotificationsPopup()
        self._overlay.add_overlay(self.notifications_popup)

        self.window_size_update_timeout = None
        self.connect("window-state-event", self._on_window_state_event)
        self.connect("configure-event", self._on_configure_event)
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _on_changes_pending(self, data=None):
        # FIXME: This is not working right.
        def songs_available_cb(available):
            if available:
                if self.views[View.ALBUM] == self.views[-1]:
                    view = self.views.pop()
                    view.destroy()
                    self._switch_to_player_view()
                    self.toolbar._search_button.set_sensitive(True)
                    self.toolbar._select_button.set_sensitive(True)
                    self.toolbar.show_stack()
            elif (self.toolbar._selectionMode is False
                  and len(self.views) != 1):
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)
                view_count = len(self.views)
                for i in range(0, view_count):
                    view = self.views.pop()
                    view.destroy()
                self.toolbar.hide_stack()
                self._switch_to_empty_view()

        grilo.songs_available(songs_available_cb)

    @log
    def _on_configure_event(self, widget, event):
        if self.window_size_update_timeout is None:
            self.window_size_update_timeout = GLib.timeout_add(
                500, self.store_window_size_and_position, widget)

    @log
    def store_window_size_and_position(self, widget):
        size = widget.get_size()
        self.settings.set_value('window-size',
                                GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position',
                                GLib.Variant('ai', [position[0], position[1]]))
        GLib.source_remove(self.window_size_update_timeout)
        self.window_size_update_timeout = None
        return False

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean(
            'window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED'
            in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        self.proxy = Gio.DBusProxy.new_sync(
            Gio.bus_get_sync(Gio.BusType.SESSION,
                             None), Gio.DBusProxyFlags.NONE, None,
            'org.gnome.SettingsDaemon.MediaKeys',
            '/org/gnome/SettingsDaemon/MediaKeys',
            'org.gnome.SettingsDaemon.MediaKeys', None)
        self.proxy.call_sync('GrabMediaPlayerKeys',
                             GLib.Variant('(su)', ('Music', 0)),
                             Gio.DBusCallFlags.NONE, -1, None)
        self.proxy.connect('g-signal', self._handle_media_keys)

    @log
    def _windows_focus_cb(self, window, event):
        try:
            self._grab_media_player_keys()
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.
                  format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = [None] * len(View)
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        Query()
        if Query.music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed',
                             self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        did_initial_state = self.settings.get_boolean('did-initial-state')
        view_class = None
        if did_initial_state:
            view_class = EmptyView
        else:
            view_class = InitialStateView
        self.views[View.ALBUM] = view_class(self, self.player)

        self._stack.add_titled(self.views[View.ALBUM], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self.settings.set_boolean('did-initial-state', True)
        self._on_notify_model_id = self._stack.connect('notify::visible-child',
                                                       self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event',
                                                self._on_key_press)

        self.views[View.ALBUM] = AlbumsView(self, self.player)
        self.views[View.ARTIST] = ArtistsView(self, self.player)
        self.views[View.SONG] = SongsView(self, self.player)
        self.views[View.PLAYLIST] = PlaylistView(self, self.player)
        self.views[View.SEARCH] = SearchView(self, self.player)
        self.views[View.EMPTY_SEARCH] = EmptySearchView(self, self.player)

        for i in self.views:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()
        self.toolbar.dropdown.show()

        for i in self.views:
            GLib.idle_add(i.populate)

    @log
    def _select_all(self):
        if not self.toolbar._selectionMode:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
        else:
            view = self._stack.get_visible_child().get_visible_child()

        view.select_all()

    @log
    def _select_none(self):
        if not self.toolbar._selectionMode:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
            view.unselect_all()
        else:
            view = self._stack.get_visible_child().get_visible_child()
            view.select_none()

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        modifiers = (event.state & modifiers)

        if modifiers != 0:
            control_mask = Gdk.ModifierType.CONTROL_MASK
            shift_mask = Gdk.ModifierType.SHIFT_MASK
            mod1_mask = Gdk.ModifierType.MOD1_MASK

            if (event.keyval == Gdk.KEY_a and modifiers == control_mask):
                self._select_all()
            if (event.keyval == Gdk.KEY_A
                    and modifiers == (shift_mask | control_mask)):
                self._select_none()
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f and modifiers == control_mask):
                self.toolbar.searchbar.toggle()
            # Play / Pause on Ctrl + SPACE
            if (event.keyval == Gdk.KEY_space and modifiers == control_mask):
                self.player.play_pause()
            # Play previous on Ctrl + B
            if (event.keyval == Gdk.KEY_b and modifiers == control_mask):
                self.player.play_previous()
            # Play next on Ctrl + N
            if (event.keyval == Gdk.KEY_n and modifiers == control_mask):
                self.player.play_next()
            # Toggle repeat on Ctrl + R
            if (event.keyval == Gdk.KEY_r and modifiers == control_mask):
                if self.player.get_repeat_mode() == RepeatType.SONG:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SONG)
            # Toggle shuffle on Ctrl + S
            if (event.keyval == Gdk.KEY_s and modifiers == control_mask):
                if self.player.get_repeat_mode() == RepeatType.SHUFFLE:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SHUFFLE)
            # Go back from Album view on Alt + Left
            if (event.keyval == Gdk.KEY_Left and modifiers == mod1_mask):
                self.toolbar.on_back_button_clicked()
            if ((event.keyval in [Gdk.KEY_1, Gdk.KEY_KP_1])
                    and modifiers == control_mask):
                self._toggle_view(View.ALBUM)
            if ((event.keyval in [Gdk.KEY_2, Gdk.KEY_KP_2])
                    and modifiers == control_mask):
                self._toggle_view(View.ARTIST)
            if ((event.keyval in [Gdk.KEY_3, Gdk.KEY_KP_3])
                    and modifiers == control_mask):
                self._toggle_view(View.SONG)
            if ((event.keyval in [Gdk.KEY_4, Gdk.KEY_KP_4])
                    and modifiers == control_mask):
                self._toggle_view(View.PLAYLIST)
        else:
            child = self._stack.get_visible_child()
            if (event.keyval == Gdk.KEY_Delete
                    and child == self.views[View.PLAYLIST]):
                self.views[View.PLAYLIST].remove_playlist()
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.reveal(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open the search bar when typing printable chars.
        key_unic = Gdk.keyval_to_unicode(event.keyval)
        if ((not self.toolbar.searchbar.get_search_mode()
             and not event.keyval == Gdk.KEY_space)
                and GLib.unichar_isprint(chr(key_unic)) and
            (modifiers == Gdk.ModifierType.SHIFT_MASK or modifiers == 0)
                and not self.views[View.PLAYLIST].rename_active):
            self.toolbar.searchbar.reveal(True)

    @log
    def do_button_release_event(self, event):
        """Override default button release event

        :param Gdk.EventButton event: Button event
        """
        __, code = event.get_button()
        # Mouse button 8 is the navigation button
        if code == 8:
            self.toolbar.on_back_button_clicked()

    @log
    def _notify_mode_disconnect(self, data=None):
        self.player.Stop()
        self.notifications_popup.terminate_pending()
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        # Switch to all albums view when we're clicking Albums
        if (self.curr_view == self.views[View.ALBUM]
                and not (self.prev_view == self.views[View.SEARCH]
                         or self.prev_view == self.views[View.EMPTY_SEARCH])):
            self.curr_view.set_visible_child(self.curr_view._grid)

        # Slide out sidebar on switching to Artists or Playlists view
        if self.curr_view == self.views[View.ARTIST] or \
           self.curr_view == self.views[View.PLAYLIST]:
            self.curr_view.stack.set_visible_child_name('dummy')
            self.curr_view.stack.set_visible_child_name('sidebar')
        if self.curr_view != self.views[
                View.SEARCH] and self.curr_view != self.views[
                    View.EMPTY_SEARCH]:
            self.toolbar.searchbar.reveal(False)

        # Disable the selection button for the EmptySearch and Playlist
        # view
        no_selection_mode = [
            self.views[View.EMPTY_SEARCH], self.views[View.PLAYLIST]
        ]
        self.toolbar._select_button.set_sensitive(
            self.curr_view not in no_selection_mode)

        # Disable renaming playlist if it was active when leaving
        # Playlist view
        if (self.prev_view == self.views[View.PLAYLIST]
                and self.views[View.PLAYLIST].rename_active):
            self.views[View.PLAYLIST].disable_rename_playlist()

    @log
    def _toggle_view(self, view_enum):
        self._stack.set_visible_child(self.views[view_enum])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.reveal(
            button.get_active(), self.curr_view != self.views[View.SEARCH])
        if (not button.get_active()
                and (self.curr_view == self.views[View.SEARCH]
                     or self.curr_view == self.views[View.EMPTY_SEARCH])):
            child = self.curr_view.get_visible_child()
            if self.toolbar._state == ToolbarState.MAIN:
                # We should get back to the view before the search
                self._stack.set_visible_child(
                    self.views[View.SEARCH].previous_view)
            elif (self.views[View.SEARCH].previous_view
                  == self.views[View.ALBUM]
                  and child != self.curr_view._album_widget
                  and child != self.curr_view._artist_albums_widget):
                self._stack.set_visible_child(self.views[View.ALBUM])

            if self.toolbar._selectionMode:
                self.toolbar.set_selection_mode(False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode is False:
            self._on_changes_pending()
        else:
            child = self._stack.get_visible_child()
            in_playlist = (child == self.views[View.PLAYLIST])
            self.selection_toolbar._add_to_playlist_button.set_visible(
                not in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[View.PLAYLIST]:
            return

        def callback(selected_songs):
            if len(selected_songs) < 1:
                return

            playlist_dialog = PlaylistDialog(
                self, self.views[View.PLAYLIST].pls_todelete)
            if playlist_dialog.run() == Gtk.ResponseType.ACCEPT:
                playlists.add_to_playlist(playlist_dialog.get_selected(),
                                          selected_songs)
            self.toolbar.set_selection_mode(False)
            playlist_dialog.destroy()

        self._stack.get_visible_child().get_selected_songs(callback)
Esempio n. 14
0
class Window(Gtk.ApplicationWindow):
    def __repr__(self):
        return '<Window>'

    @log
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self, application=app, title=_("Music"))
        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        selectAll = Gio.SimpleAction.new('selectAll', None)
        app.add_accelerator('<Primary>a', 'win.selectAll', None)
        selectAll.connect('activate', self._on_select_all)
        self.add_action(selectAll)
        selectNone = Gio.SimpleAction.new('selectNone', None)
        selectNone.connect('activate', self._on_select_none)
        self.add_action(selectNone)
        self.set_size_request(200, 100)
        self.set_icon_name('gnome-music')
        self.notification_handler = None
        self._loading_counter = 0

        self.prev_view = None
        self.curr_view = None

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(
                size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self._setup_view()
        self._setup_loading_notification()
        self._setup_playlist_notification()

        self.window_size_update_timeout = None
        self.connect("window-state-event", self._on_window_state_event)
        self.connect("configure-event", self._on_configure_event)
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _setup_loading_notification(self):
        self._loading_notification = Gtk.Revealer(halign=Gtk.Align.CENTER,
                                                  valign=Gtk.Align.START)
        self._loading_notification.set_transition_type(
            Gtk.RevealerTransitionType.SLIDE_DOWN)
        self._overlay.add_overlay(self._loading_notification)

        grid = Gtk.Grid(margin_bottom=18, margin_start=18, margin_end=18)
        grid.set_column_spacing(18)
        grid.get_style_context().add_class('app-notification')

        spinner = Gtk.Spinner()
        spinner.start()
        grid.add(spinner)

        label = Gtk.Label.new(_("Loading"))
        grid.add(label)

        self._loading_notification.add(grid)
        self._loading_notification.show_all()

    @log
    def _setup_playlist_notification(self):
        self._playlist_notification_timeout_id = 0
        self._playlist_notification = Gtk.Revealer(halign=Gtk.Align.CENTER,
                                                   valign=Gtk.Align.START)
        self._playlist_notification.set_transition_type(
            Gtk.RevealerTransitionType.SLIDE_DOWN)
        self._overlay.add_overlay(self._playlist_notification)

        grid = Gtk.Grid(margin_bottom=18, margin_start=18, margin_end=18)
        grid.set_column_spacing(12)
        grid.get_style_context().add_class('app-notification')

        def remove_notification_timeout(self):
            # Remove the timeout if any
            if self._playlist_notification_timeout_id > 0:
                GLib.source_remove(self._playlist_notification_timeout_id)
                self._playlist_notification_timeout_id = 0

        # Undo playlist removal
        def undo_remove_cb(button, self):
            self._playlist_notification.set_reveal_child(False)
            self.views[3].undo_playlist_deletion()

            remove_notification_timeout(self)

        # Playlist name label
        self._playlist_notification.label = Gtk.Label('')
        grid.add(self._playlist_notification.label)

        # Undo button
        undo_button = Gtk.Button.new_with_mnemonic(_("_Undo"))
        undo_button.connect("clicked", undo_remove_cb, self)
        grid.add(undo_button)

        self._playlist_notification.add(grid)
        self._playlist_notification.show_all()

    @log
    def _on_changes_pending(self, data=None):
        def songs_available_cb(available):
            if available:
                if self.views[0] == self.views[-1]:
                    view = self.views.pop()
                    view.destroy()
                    self._switch_to_player_view()
                    self.toolbar._search_button.set_sensitive(True)
                    self.toolbar._select_button.set_sensitive(True)
                    self.toolbar.show_stack()
            elif (self.toolbar._selectionMode is False
                  and len(self.views) != 1):
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)
                view_count = len(self.views)
                for i in range(0, view_count):
                    view = self.views.pop()
                    view.destroy()
                self.toolbar.hide_stack()
                self._switch_to_empty_view()

        grilo.songs_available(songs_available_cb)

    @log
    def _on_configure_event(self, widget, event):
        if self.window_size_update_timeout is None:
            self.window_size_update_timeout = GLib.timeout_add(
                500, self.store_window_size_and_position, widget)

    @log
    def store_window_size_and_position(self, widget):
        size = widget.get_size()
        self.settings.set_value('window-size',
                                GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position',
                                GLib.Variant('ai', [position[0], position[1]]))
        GLib.source_remove(self.window_size_update_timeout)
        self.window_size_update_timeout = None
        return False

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean(
            'window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED'
            in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        self.proxy = Gio.DBusProxy.new_sync(
            Gio.bus_get_sync(Gio.BusType.SESSION,
                             None), Gio.DBusProxyFlags.NONE, None,
            'org.gnome.SettingsDaemon.MediaKeys',
            '/org/gnome/SettingsDaemon/MediaKeys',
            'org.gnome.SettingsDaemon.MediaKeys', None)
        self.proxy.call_sync('GrabMediaPlayerKeys',
                             GLib.Variant('(su)', ('Music', 0)),
                             Gio.DBusCallFlags.NONE, -1, None)
        self.proxy.connect('g-signal', self._handle_media_keys)

    @log
    def _windows_focus_cb(self, window, event):
        try:
            self._grab_media_player_keys()
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.
                  format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)

        # Add the 'background' styleclass so it properly hides the
        # bottom line of the searchbar
        self._stack.get_style_context().add_class('background')

        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)

        def songs_available_cb(available):
            if available:
                self._switch_to_player_view()
            else:
                self._switch_to_empty_view()

        Query()
        if Query.music_folder:
            grilo.songs_available(songs_available_cb)
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed',
                             self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        did_initial_state = self.settings.get_boolean('did-initial-state')
        view_class = None
        if did_initial_state:
            view_class = EmptyView
        else:
            view_class = InitialStateView
        self.views.append(view_class(self, self.player))

        self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self.settings.set_boolean('did-initial-state', True)
        self._on_notify_model_id = self._stack.connect('notify::visible-child',
                                                       self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event',
                                                self._on_key_press)

        self.views.append(AlbumsView(self, self.player))
        self.views.append(ArtistsView(self, self.player))
        self.views.append(SongsView(self, self.player))
        self.views.append(PlaylistView(self, self.player))
        self.views.append(SearchView(self, self.player))
        self.views.append(EmptySearchView(self, self.player))

        for i in self.views:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()
        self.toolbar.dropdown.show()

        for i in self.views:
            GLib.idle_add(i.populate)

    @log
    def _on_select_all(self, action, param):
        if self.toolbar._selectionMode is False:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
        else:
            view = self._stack.get_visible_child().get_visible_child()

        view.select_all()

    @log
    def _on_select_none(self, action, param):
        if self.toolbar._state == ToolbarState.MAIN:
            view = self._stack.get_visible_child()
            view.unselect_all()
        else:
            view = self._stack.get_visible_child().get_visible_child()
            view.select_none()

    @log
    def show_playlist_notification(self):
        """Show a notification on playlist removal and provide an
        option to undo for 5 seconds.
        """

        # Callback to remove playlists
        def remove_playlist_timeout_cb(self):
            # Remove the playlist
            playlist.delete_playlist(self.views[3].pl_todelete)

            # Hide the notification
            self._playlist_notification.set_reveal_child(False)

            # Stop the timeout
            self._playlist_notification_timeout_id = 0

            return GLib.SOURCE_REMOVE

        # If a notification is already visible, remove that playlist
        if self._playlist_notification_timeout_id > 0:
            GLib.source_remove(self._playlist_notification_timeout_id)
            remove_playlist_timeout_cb(self)

        playlist_title = self.views[3].current_playlist.get_title()
        label = _("Playlist {} removed".format(playlist_title))

        self._playlist_notification.label.set_label(label)
        self._playlist_notification.set_reveal_child(True)

        timeout_id = GLib.timeout_add_seconds(5, remove_playlist_timeout_cb,
                                              self)
        self._playlist_notification_timeout_id = timeout_id

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        event_and_modifiers = (event.state & modifiers)

        if event_and_modifiers != 0:
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.toolbar.searchbar.toggle_bar()
            # Play / Pause on Ctrl + SPACE
            if (event.keyval == Gdk.KEY_space
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.player.play_pause()
            # Play previous on Ctrl + B
            if (event.keyval == Gdk.KEY_b
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.player.play_previous()
            # Play next on Ctrl + N
            if (event.keyval == Gdk.KEY_n
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.player.play_next()
            # Toggle repeat on Ctrl + R
            if (event.keyval == Gdk.KEY_r
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                if self.player.get_repeat_mode() == RepeatType.SONG:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SONG)
            # Toggle shuffle on Ctrl + S
            if (event.keyval == Gdk.KEY_s
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                if self.player.get_repeat_mode() == RepeatType.SHUFFLE:
                    self.player.set_repeat_mode(RepeatType.NONE)
                else:
                    self.player.set_repeat_mode(RepeatType.SHUFFLE)
            # Go back from Album view on Alt + Left
            if (event.keyval == Gdk.KEY_Left
                    and event_and_modifiers == Gdk.ModifierType.MOD1_MASK):
                if (self.toolbar._state != ToolbarState.MAIN):
                    self.curr_view.set_visible_child(self.curr_view._grid)
                    self.toolbar.set_state(ToolbarState.MAIN)
            # Go to Albums view on Ctrl + 1
            if (event.keyval == Gdk.KEY_1
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 0)
            # Go to Artists view on Ctrl + 2
            if (event.keyval == Gdk.KEY_2
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 1)
            # Go to Songs view on Ctrl + 3
            if (event.keyval == Gdk.KEY_3
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 2)
            # Go to Playlists view on Ctrl + 4
            if (event.keyval == Gdk.KEY_4
                    and event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self._toggle_view(0, 3)
        else:
            if (event.keyval == Gdk.KEY_Delete):
                if self._stack.get_visible_child() == self.views[3]:
                    self.views[3].remove_playlist()
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.show_bar(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open the search bar when typing printable chars.
        key_unic = Gdk.keyval_to_unicode(event.keyval)
        if ((not self.toolbar.searchbar.get_search_mode()
             and not event.keyval == Gdk.KEY_space)
                and GLib.unichar_isprint(chr(key_unic))
                and (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK
                     or event_and_modifiers == 0)):
            self.toolbar.searchbar.show_bar(True)

    @log
    def _notify_mode_disconnect(self, data=None):
        self.player.Stop()
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        # Switch to all albums view when we're clicking Albums
        if self.curr_view == self.views[0] and not (
                self.prev_view == self.views[4]
                or self.prev_view == self.views[5]):
            self.curr_view.set_visible_child(self.curr_view._grid)

        # Slide out sidebar on switching to Artists or Playlists view
        if self.curr_view == self.views[1] or \
           self.curr_view == self.views[3]:
            self.curr_view.stack.set_visible_child_name('dummy')
            self.curr_view.stack.set_visible_child_name('sidebar')
        if self.curr_view != self.views[4] and self.curr_view != self.views[5]:
            self.toolbar.searchbar.show_bar(False)

        # Toggle the selection button for the EmptySearch view
        if self.curr_view == self.views[5] or \
           self.prev_view == self.views[5]:
            self.toolbar._select_button.set_sensitive(
                not self.toolbar._select_button.get_sensitive())

    @log
    def _toggle_view(self, btn, i):
        self._stack.set_visible_child(self.views[i])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.show_bar(button.get_active(),
                                        self.curr_view != self.views[4])
        if (not button.get_active() and (self.curr_view == self.views[4]
                                         or self.curr_view == self.views[5])):
            if self.toolbar._state == ToolbarState.MAIN:
                # We should get back to the view before the search
                self._stack.set_visible_child(self.views[4].previous_view)
            elif (self.views[4].previous_view == self.views[0]
                  and self.curr_view.get_visible_child() !=
                  self.curr_view._albumWidget
                  and self.curr_view.get_visible_child() !=
                  self.curr_view._artistAlbumsWidget):
                self._stack.set_visible_child(self.views[0])

            if self.toolbar._selectionMode:
                self.toolbar.set_selection_mode(False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode is False:
            self._on_changes_pending()
        else:
            in_playlist = self._stack.get_visible_child() == self.views[3]
            self.selection_toolbar._add_to_playlist_button.set_visible(
                not in_playlist)
            self.selection_toolbar._remove_from_playlist_button.set_visible(
                in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[3]:
            return

        def callback(selected_songs):
            if len(selected_songs) < 1:
                return

            playlist_dialog = PlaylistDialog(self, self.views[3].pl_todelete)
            if playlist_dialog.run() == Gtk.ResponseType.ACCEPT:
                playlist.add_to_playlist(playlist_dialog.get_selected(),
                                         selected_songs)
            self.toolbar.set_selection_mode(False)
            playlist_dialog.destroy()

        self._stack.get_visible_child().get_selected_songs(callback)

    @log
    def _on_remove_from_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() != self.views[3]:
            return

        def callback(selected_songs):
            if len(selected_songs) < 1:
                return

            playlist.remove_from_playlist(self.views[3].current_playlist,
                                          selected_songs)
            self.toolbar.set_selection_mode(False)

        self._stack.get_visible_child().get_selected_songs(callback)

    @log
    def push_loading_notification(self):
        """ Increases the counter of loading notification triggers
        running. If there is no notification is visible, the loading
        notification is started.
        """
        def show_notification_cb(self):
            self._loading_notification.set_reveal_child(True)
            self._show_notification_timeout_id = 0
            return GLib.SOURCE_REMOVE

        if self._loading_counter == 0:
            # Only show the notification after a small delay, thus
            # add a timeout. 500ms feels good enough.
            self._show_notification_timeout_id = GLib.timeout_add(
                500, show_notification_cb, self)

        self._loading_counter = self._loading_counter + 1

    @log
    def pop_loading_notification(self):
        """ Decreases the counter of loading notification triggers
        running. If it reaches zero, the notification is withdrawn.
        """
        self._loading_counter = self._loading_counter - 1

        if self._loading_counter == 0:
            # Remove the previously set timeout, if any
            if self._show_notification_timeout_id > 0:
                GLib.source_remove(self._show_notification_timeout_id)
                self._show_notification_timeout_id = 0

            self._loading_notification.set_reveal_child(False)
Esempio n. 15
0
class Window(Gtk.ApplicationWindow):

    def __repr__(self):
        return '<Window>'

    @log
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self,
                                       application=app,
                                       title=_("Music"))
        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        selectAll = Gio.SimpleAction.new('selectAll', None)
        app.add_accelerator('<Primary>a', 'win.selectAll', None)
        selectAll.connect('activate', self._on_select_all)
        self.add_action(selectAll)
        selectNone = Gio.SimpleAction.new('selectNone', None)
        selectNone.connect('activate', self._on_select_none)
        self.add_action(selectNone)
        self.set_size_request(200, 100)
        self.set_icon_name('gnome-music')
        self.notification_handler = None

        self.prev_view = None
        self.curr_view = None
        self.pl_todelete_notification = None

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self._setup_view()

        self.window_size_update_timeout = None
        self.connect("window-state-event", self._on_window_state_event)
        self.connect("configure-event", self._on_configure_event)
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _on_changes_pending(self, data=None):
        count = 1
        cursor = tracker.query(Query.all_songs_count(), None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if not count > 0:
            if self.toolbar._selectionMode is False and len(self.views) != 1:
                self._stack.disconnect(self._on_notify_model_id)
                self.disconnect(self._key_press_event_id)
                view_count = len(self.views)
                for i in range(0, view_count):
                    view = self.views.pop()
                    view.destroy()
                self.toolbar.hide_stack()
                self._switch_to_empty_view()
        else:
            if (self.views[0] == self.views[-1]):
                view = self.views.pop()
                view.destroy()
                self._switch_to_player_view()
                self.toolbar._search_button.set_sensitive(True)
                self.toolbar._select_button.set_sensitive(True)
                self.toolbar.show_stack()

    def _on_configure_event(self, widget, event):
        if self.window_size_update_timeout is None:
            self.window_size_update_timeout = GLib.timeout_add(500, self.store_window_size_and_position, widget)

    @log
    def store_window_size_and_position(self, widget):
        size = widget.get_size()
        self.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))
        GLib.source_remove(self.window_size_update_timeout)
        self.window_size_update_timeout = None
        return False

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        self.proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
                                            Gio.DBusProxyFlags.NONE,
                                            None,
                                            'org.gnome.SettingsDaemon',
                                            '/org/gnome/SettingsDaemon/MediaKeys',
                                            'org.gnome.SettingsDaemon.MediaKeys',
                                            None)
        self.proxy.call_sync('GrabMediaPlayerKeys',
                             GLib.Variant('(su)', ('Music', 0)),
                             Gio.DBusCallFlags.NONE,
                             -1,
                             None)
        self.proxy.connect('g-signal', self._handle_media_keys)

    @log
    def _windows_focus_cb(self, window, event):
        try:
            self._grab_media_player_keys()
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.player = Player(self)
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)
        self._overlay = Gtk.Overlay(child=self._stack)
        self._overlay.add_overlay(self.toolbar.dropdown)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._overlay, True, True, 0)
        self._box.pack_start(self.player.actionbar, False, False, 0)
        self._box.pack_start(self.selection_toolbar.actionbar, False, False, 0)
        self.add(self._box)
        count = 0
        cursor = None

        Query()
        if Query.music_folder:
            try:
                cursor = tracker.query(Query.all_songs_count(), None)
                if cursor is not None and cursor.next(None):
                    count = cursor.get_integer(0)
            except Exception as e:
                logger.error("Tracker query crashed: %s", e)
                count = 0

            if count > 0:
                self._switch_to_player_view()
            # To revert to the No Music View when no songs are found
            else:
                if self.toolbar._selectionMode is False:
                    self._switch_to_empty_view()
        else:
            # Revert to No Music view if XDG dirs are not set
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.MAIN)
        self.toolbar.header_bar.show()
        self._overlay.show()
        self.player.actionbar.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        did_initial_state = self.settings.get_boolean('did-initial-state')
        view_class = None
        if did_initial_state:
            view_class = Views.Empty
        else:
            view_class = Views.InitialState
        self.views.append(view_class(self, self.player))

        self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self.settings.set_boolean('did-initial-state', True)
        self._on_notify_model_id = self._stack.connect('notify::visible-child', self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event', self._on_key_press)

        self.views.append(Views.Albums(self, self.player))
        self.views.append(Views.Artists(self, self.player))
        self.views.append(Views.Songs(self, self.player))
        self.views.append(Views.Playlist(self, self.player))
        self.views.append(Views.Search(self, self.player))
        self.views.append(Views.EmptySearch(self, self.player))

        for i in self.views:
            if i.title:
                self._stack.add_titled(i, i.name, i.title)
            else:
                self._stack.add_named(i, i.name)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()
        self.toolbar.dropdown.show()

        self.views[0].populate()

    @log
    def _set_selection(self, model, value, parent=None):
        count = 0
        _iter = model.iter_children(parent)
        while _iter is not None:
            if model.iter_has_child(_iter):
                count += self._set_selection(model, value, _iter)
            if model[_iter][5]:
                model.set(_iter, [6], [value])
                count += 1
            _iter = model.iter_next(_iter)
        return count

    @log
    def _on_select_all(self, action, param):
        if self.toolbar._selectionMode is False:
            return
        if self.toolbar._state == ToolbarState.MAIN:
            model = self._stack.get_visible_child().model
        else:
            model = self._stack.get_visible_child().get_visible_child().model
        count = self._set_selection(model, True)
        if count > 0:
            self.toolbar._selection_menu_label.set_text(
                ngettext("Selected %d item", "Selected %d items", count) % count)
            self.selection_toolbar._add_to_playlist_button.set_sensitive(True)
            self.selection_toolbar._remove_from_playlist_button.set_sensitive(True)
        elif count == 0:
            self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    @log
    def _on_select_none(self, action, param):
        if self.toolbar._state == ToolbarState.MAIN:
            model = self._stack.get_visible_child().model
        else:
            model = self._stack.get_visible_child().get_visible_child().model
        self._set_selection(model, False)
        self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
        self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
        self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    def _show_notification(self):
        self.notification_handler = None
        self.notification.show_all()
        return False

    @log
    def _init_loading_notification(self):
        self.notification = Gd.Notification()
        self.notification.set_timeout(5)
        grid = Gtk.Grid(valign=Gtk.Align.CENTER, margin_end=8)
        grid.set_column_spacing(8)
        spinner = Gtk.Spinner()
        spinner.start()
        grid.add(spinner)
        label = Gtk.Label.new(_("Loading"))
        grid.add(label)
        self.notification.add(grid)
        self._overlay.add_overlay(self.notification)
        if self.notification_handler:
            GLib.Source.remove(self.notification_handler)
            self.notification_handler = None
        self.notification_handler = GLib.timeout_add(1000, self._show_notification)

    @log
    def _init_playlist_removal_notification(self):
        if self.pl_todelete_notification:
            self.views[3].really_delete = False
            self.pl_todelete_notification.destroy()
            Views.playlists.delete_playlist(self.views[3].pl_todelete)

        self.notification = Gd.Notification()
        self.notification.set_timeout(20)

        grid = Gtk.Grid(valign=Gtk.Align.CENTER, margin_right=8)
        grid.set_column_spacing(8)
        self.notification.add(grid)

        undo_button = Gtk.Button.new_with_mnemonic(_("_Undo"))
        label = _("Playlist %s removed" % (
            self.views[3].current_playlist.get_title()))
        grid.add(Gtk.Label.new(label))
        grid.add(undo_button)

        self.notification.show_all()
        self._overlay.add_overlay(self.notification)
        self.pl_todelete_notification = self.notification

        self.notification.connect("dismissed", self._playlist_removal_notification_dismissed)
        undo_button.connect("clicked", self._undo_deletion)

    @log
    def _playlist_removal_notification_dismissed(self, widget):
        self.pl_todelete_notification = None
        if self.views[3].really_delete:
            Views.playlists.delete_playlist(self.views[3].pl_todelete)
        else:
            self.views[3].really_delete = True

    @log
    def _undo_deletion(self, widget):
        self.views[3].really_delete = False
        self.notification.dismiss()
        self.views[3].undo_playlist_deletion()

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        event_and_modifiers = (event.state & modifiers)

        if event_and_modifiers != 0:
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f and
                    event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.toolbar.searchbar.toggle_bar()
            # Go back from Album view on Alt + Left
            if (event.keyval == Gdk.KEY_Left and
                    event_and_modifiers == Gdk.ModifierType.MOD1_MASK):
                if (self.toolbar._state != ToolbarState.MAIN):
                    self.curr_view.set_visible_child(self.curr_view._grid)
                    self.toolbar.set_state(ToolbarState.MAIN)
        else:
            if (event.keyval == Gdk.KEY_Delete):
                if self._stack.get_visible_child() == self.views[3]:
                    self.views[3]._on_delete_activate(None)
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.show_bar(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open search bar when typing printable chars if it not opened
        # Make sure we skip unprintable chars and don't grab space press
        # (this is used for play/pause)
        if not self.toolbar.searchbar.get_reveal_child() and not event.keyval == Gdk.KEY_space:
            if (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK or
                    event_and_modifiers == 0) and \
                    GLib.unichar_isprint(chr(Gdk.keyval_to_unicode(event.keyval))):
                self.toolbar.searchbar.show_bar(True)
        else:
            if not self.toolbar.searchbar.get_reveal_child():
                if event.keyval == Gdk.KEY_space and self.player.actionbar.get_visible():
                    if self.get_focus() != self.player.playBtn:
                        self.player.play_pause()

    @log
    def _notify_mode_disconnect(self, data=None):
        self.player.Stop()
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        self.prev_view = self.curr_view
        self.curr_view = stack.get_visible_child()

        # Switch to all albums view when we're clicking Albums
        if self.curr_view == self.views[0] and not (self.prev_view == self.views[4] or self.prev_view == self.views[5]):
            self.curr_view.set_visible_child(self.curr_view._grid)

        # Slide out sidebar on switching to Artists or Playlists view
        if self.curr_view == self.views[1] or \
           self.curr_view == self.views[3]:
            self.curr_view.stack.set_visible_child_name('dummy')
            self.curr_view.stack.set_visible_child_name('sidebar')
        if self.curr_view != self.views[4] and self.curr_view != self.views[5]:
            self.toolbar.searchbar.show_bar(False)

        # Toggle the selection button for the EmptySearch view
        if self.curr_view == self.views[5] or \
           self.prev_view == self.views[5]:
            self.toolbar._select_button.set_sensitive(
                not self.toolbar._select_button.get_sensitive())

    @log
    def _toggle_view(self, btn, i):
        self._stack.set_visible_child(self.views[i])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.show_bar(button.get_active(),
                                        self.curr_view != self.views[4])
        if (not button.get_active() and
                (self.curr_view == self.views[4] or self.curr_view == self.views[5])):
            if self.toolbar._state == ToolbarState.MAIN:
                # We should get back to the view before the search
                self._stack.set_visible_child(self.views[4].previous_view)
            elif (self.views[4].previous_view == self.views[0] and
                 self.curr_view.get_visible_child() != self.curr_view._albumWidget and
                 self.curr_view.get_visible_child() != self.curr_view._artistAlbumsWidget):
                self._stack.set_visible_child(self.views[0])

            if self.toolbar._selectionMode:
                self.toolbar.set_selection_mode(False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode is False:
            self._on_changes_pending()
        else:
            in_playlist = self._stack.get_visible_child() == self.views[3]
            self.selection_toolbar._add_to_playlist_button.set_visible(not in_playlist)
            self.selection_toolbar._remove_from_playlist_button.set_visible(in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            add_to_playlist = Widgets.PlaylistDialog(self)
            if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
                playlist.add_to_playlist(
                    add_to_playlist.get_selected(),
                    selected_tracks)
            self.toolbar.set_selection_mode(False)
            add_to_playlist.dialog_box.destroy()

        self._stack.get_visible_child().get_selected_tracks(callback)

    @log
    def _on_remove_from_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() != self.views[3]:
            return

        def callback(selected_tracks):
            if len(selected_tracks) < 1:
                return

            playlist.remove_from_playlist(
                self.views[3].current_playlist,
                selected_tracks)
            self.toolbar.set_selection_mode(False)

        self._stack.get_visible_child().get_selected_tracks(callback)
Esempio n. 16
0
class Window(Gtk.ApplicationWindow):

    @log
    def __init__(self, app):
        Gtk.ApplicationWindow.__init__(self,
                                       application=app,
                                       title=_("Music"))
        self.connect('focus-in-event', self._windows_focus_cb)
        self.settings = Gio.Settings.new('org.gnome.Music')
        self.add_action(self.settings.create_action('repeat'))
        selectAll = Gio.SimpleAction.new('selectAll', None)
        app.add_accelerator('<Primary>a', 'win.selectAll', None)
        selectAll.connect('activate', self._on_select_all)
        self.add_action(selectAll)
        selectNone = Gio.SimpleAction.new('selectNone', None)
        selectNone.connect('activate', self._on_select_none)
        self.add_action(selectNone)
        self.set_size_request(200, 100)
        self.set_icon_name('gnome-music')

        size_setting = self.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
            self.resize(size_setting[0], size_setting[1])

        position_setting = self.settings.get_value('window-position')
        if len(position_setting) == 2 \
           and isinstance(position_setting[0], int) \
           and isinstance(position_setting[1], int):
            self.move(position_setting[0], position_setting[1])

        if self.settings.get_value('window-maximized'):
            self.maximize()

        self.connect("window-state-event", self._on_window_state_event)
        self.connect("configure-event", self._on_configure_event)
        self._setup_view()
        self.proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
                                            Gio.DBusProxyFlags.NONE,
                                            None,
                                            'org.gnome.SettingsDaemon',
                                            '/org/gnome/SettingsDaemon/MediaKeys',
                                            'org.gnome.SettingsDaemon.MediaKeys',
                                            None)
        self._grab_media_player_keys()
        try:
            self.proxy.connect('g-signal', self._handle_media_keys)
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass
        grilo.connect('changes-pending', self._on_changes_pending)

    @log
    def _on_changes_pending(self, data=None):
        count = 1
        cursor = tracker.query(Query.SONGS_COUNT, None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if not count > 0:
            print("switching to Empty view")
            self._stack.disconnect(self._on_notify_model_id)
            self.disconnect(self._key_press_event_id)
            for i in range(0, 4):
                view = self.views.pop()
                view.destroy()
            self.toolbar.hide_stack()
            self._switch_to_empty_view()
        else:
            if (self.views[0] == self.views[-1]):
                print("switching to player view")
                view = self.views.pop()
                view.destroy()
                self._switch_to_player_view()
                self.toolbar._search_button.set_sensitive(True)
                self.toolbar._select_button.set_sensitive(True)
                self.toolbar.show_stack()

    @log
    def _on_configure_event(self, widget, event):
        size = widget.get_size()
        self.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

        position = widget.get_position()
        self.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))

    @log
    def _on_window_state_event(self, widget, event):
        self.settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

    @log
    def _grab_media_player_keys(self):
        try:
            self.proxy.call_sync('GrabMediaPlayerKeys',
                                 GLib.Variant('(su)', ('Music', 0)),
                                 Gio.DBusCallFlags.NONE,
                                 -1,
                                 None)
        except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
            pass

    @log
    def _windows_focus_cb(self, window, event):
        self._grab_media_player_keys()

    @log
    def _handle_media_keys(self, proxy, sender, signal, parameters):
        if signal != 'MediaPlayerKeyPressed':
            print('Received an unexpected signal \'%s\' from media player'.format(signal))
            return
        response = parameters.get_child_value(1).get_string()
        if 'Play' in response:
            self.player.play_pause()
        elif 'Stop' in response:
            self.player.Stop()
        elif 'Next' in response:
            self.player.play_next()
        elif 'Previous' in response:
            self.player.play_previous()

    @log
    def _setup_view(self):
        self._box = Gtk.VBox()
        self.player = Player()
        self.selection_toolbar = SelectionToolbar()
        self.toolbar = Toolbar()
        self.views = []
        self._stack = Gtk.Stack(
            transition_type=Gtk.StackTransitionType.CROSSFADE,
            transition_duration=100,
            visible=True,
            can_focus=False)
        self.set_titlebar(self.toolbar.header_bar)
        self._box.pack_start(self.toolbar.searchbar, False, False, 0)
        self._box.pack_start(self._stack, True, True, 0)
        self._box.pack_start(self.player.eventBox, False, False, 0)
        self._box.pack_start(self.selection_toolbar.eventbox, False, False, 0)
        self.add(self._box)
        count = 1
        cursor = tracker.query(Query.SONGS_COUNT, None)
        if cursor is not None and cursor.next(None):
            count = cursor.get_integer(0)
        if count > 0:
            self._switch_to_player_view()
        # To revert to the No Music View when no songs are found
        else:
            self._switch_to_empty_view()

        self.toolbar._search_button.connect('toggled', self._on_search_toggled)
        self.toolbar.connect('selection-mode-changed', self._on_selection_mode_changed)
        self.selection_toolbar._add_to_playlist_button.connect(
            'clicked', self._on_add_to_playlist_button_clicked)
        self.selection_toolbar._remove_from_playlist_button.connect(
            'clicked', self._on_remove_from_playlist_button_clicked)

        self.toolbar.set_state(ToolbarState.ALBUMS)
        self.toolbar.header_bar.show()
        self.player.eventBox.show_all()
        self._box.show()
        self.show()

    @log
    def _switch_to_empty_view(self):
        self.views.append(Views.Empty(self.toolbar, self.player))
        self._stack.add_titled(self.views[0], _("Empty"), _("Empty"))
        self.toolbar._search_button.set_sensitive(False)
        self.toolbar._select_button.set_sensitive(False)

    @log
    def _switch_to_player_view(self):
        self.views.append(Views.Albums(self.toolbar, self.selection_toolbar, self.player))
        self.views.append(Views.Artists(self.toolbar, self.selection_toolbar, self.player))
        self.views.append(Views.Songs(self.toolbar, self.selection_toolbar, self.player))
        self.views.append(Views.Playlist(self.toolbar, self.selection_toolbar, self.player))

        for i in self.views:
            self._stack.add_titled(i, i.title, i.title)

        self.toolbar.set_stack(self._stack)
        self.toolbar.searchbar.show()

        self._on_notify_model_id = self._stack.connect('notify::visible-child', self._on_notify_mode)
        self.connect('destroy', self._notify_mode_disconnect)
        self._key_press_event_id = self.connect('key_press_event', self._on_key_press)

        self.views[0].populate()

    @log
    def _on_select_all(self, action, param):
        if self.toolbar._selectionMode is False:
            return
        if self.toolbar._state != ToolbarState.SINGLE:
            model = self._stack.get_visible_child()._model
        else:
            model = self._stack.get_visible_child()._albumWidget.model
        _iter = model.get_iter_first()
        count = 0
        while _iter is not None:
            model.set(_iter, [6], [True])
            _iter = model.iter_next(_iter)
            count = count + 1
        if count > 0:
            self.toolbar._selection_menu_label.set_text(
                ngettext("Selected %d item", "Selected %d items", count) % count)
            self.selection_toolbar._add_to_playlist_button.set_sensitive(True)
            self.selection_toolbar._remove_from_playlist_button.set_sensitive(True)
        elif count == 0:
            self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    @log
    def _on_select_none(self, action, param):
        if self.toolbar._state != ToolbarState.SINGLE:
            model = self._stack.get_visible_child()._model
        else:
            model = self._stack.get_visible_child()._albumWidget.model
        _iter = model.get_iter_first()
        self.selection_toolbar._add_to_playlist_button.set_sensitive(False)
        self.selection_toolbar._remove_from_playlist_button.set_sensitive(False)
        while _iter is not None:
            model.set(_iter, [6], [False])
            _iter = model.iter_next(_iter)
        self.toolbar._selection_menu_label.set_text(_("Click on items to select them"))
        self._stack.get_visible_child().queue_draw()

    @log
    def _on_key_press(self, widget, event):
        modifiers = Gtk.accelerator_get_default_mod_mask()
        event_and_modifiers = (event.state & modifiers)

        if event_and_modifiers != 0:
            # Open search bar on Ctrl + F
            if (event.keyval == Gdk.KEY_f and
                    event_and_modifiers == Gdk.ModifierType.CONTROL_MASK):
                self.toolbar.searchbar.toggle_bar()
        else:
            # Close search bar after Esc is pressed
            if event.keyval == Gdk.KEY_Escape:
                self.toolbar.searchbar.show_bar(False)
                # Also disable selection
                if self.toolbar._selectionMode:
                    self.toolbar.set_selection_mode(False)

        # Open search bar when typing printable chars if it not opened
        # Make sure we skip unprintable chars and don't grab space press
        # (this is used for play/pause)
        if not self.toolbar.searchbar.get_reveal_child() and not event.keyval == Gdk.KEY_space:
            if (event_and_modifiers == Gdk.ModifierType.SHIFT_MASK or
                    event_and_modifiers == 0) and \
                    GLib.unichar_isprint(chr(Gdk.keyval_to_unicode(event.keyval))):
                self.toolbar.searchbar.show_bar(True)
        else:
            if not self.toolbar.searchbar.get_reveal_child():
                if event.keyval == Gdk.KEY_space and self.player.eventBox.get_visible():
                    self.player.play_pause()

    @log
    def _notify_mode_disconnect(self, data=None):
        self._stack.disconnect(self._on_notify_model_id)

    @log
    def _on_notify_mode(self, stack, param):
        # Slide out artist list on switching to artists view
        if stack.get_visible_child() == self.views[1] or \
           stack.get_visible_child() == self.views[3]:
            stack.get_visible_child().stack.set_visible_child_name('dummy')
            stack.get_visible_child().stack.set_visible_child_name('sidebar')
        self.toolbar.searchbar.show_bar(False)

    @log
    def _toggle_view(self, btn, i):
        self._stack.set_visible_child(self.views[i])

    @log
    def _on_search_toggled(self, button, data=None):
        self.toolbar.searchbar.show_bar(button.get_active())

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self.toolbar._selectionMode:
            in_playlist = self._stack.get_visible_child() == self.views[3]
            self.selection_toolbar._add_to_playlist_button.set_visible(not in_playlist)
            self.selection_toolbar._remove_from_playlist_button.set_visible(in_playlist)

    @log
    def _on_add_to_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() == self.views[3]:
            return

        def callback(selected_uris):
            if len(selected_uris) < 1:
                return

            add_to_playlist = Widgets.PlaylistDialog(self)
            if add_to_playlist.dialog_box.run() == Gtk.ResponseType.ACCEPT:
                playlist.add_to_playlist(
                    add_to_playlist.get_selected(),
                    selected_uris)
            self.toolbar.set_selection_mode(False)
            add_to_playlist.dialog_box.destroy()

        self._stack.get_visible_child().get_selected_track_uris(callback)

    @log
    def _on_remove_from_playlist_button_clicked(self, widget):
        if self._stack.get_visible_child() != self.views[3]:
            return

        def callback(selected_uris):
            if len(selected_uris) < 1:
                return

            playlist.remove_from_playlist(
                self.views[3].current_playlist,
                selected_uris)
            self.toolbar.set_selection_mode(False)

        self._stack.get_visible_child().get_selected_track_uris(callback)