def __init__(self, window, player):
        super().__init__('search', None, window, Gd.MainViewType.LIST)

        scale = self.get_scale_factor()
        loading_icon_surface = DefaultIcon(scale).get(
            DefaultIcon.Type.loading, ArtSize.SMALL)
        self._loading_icon = Gdk.pixbuf_get_from_surface(
            loading_icon_surface, 0, 0, loading_icon_surface.get_width(),
            loading_icon_surface.get_height())

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._view.get_generic_view().set_show_expanders(False)
        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None
Beispiel #2
0
    def __init__(self, window, player):
        BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST)
        self._items = {}
        self.isStarred = None
        self.iter_to_clean = None

        scale = self.get_scale_factor()
        loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
                                                      ArtSize.small)
        self._loading_icon = Gdk.pixbuf_get_from_surface(
            loading_icon_surface, 0, 0, loading_icon_surface.get_width(),
            loading_icon_surface.get_height())

        self._add_list_renderers()
        self.player = player
        self.head_iters = [None, None, None, None]
        self.songs_model = self.model
        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self.albums_selected = []
        self._albums = {}
        self._albumWidget = AlbumWidget(player, self)
        self.add(self._albumWidget)

        self.artists_albums_selected = []
        self._artists = {}
        self._artistAlbumsWidget = None

        self._view.get_generic_view().set_show_expanders(False)
        self.items_selected = []
        self.items_selected_callback = None

        self.found_items_number = None
Beispiel #3
0
    def __init__(self, window, player):
        super().__init__('search', None, window)

        # FIXME: Searchbar handling does not belong here.
        self._searchbar = window._searchbar

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None
Beispiel #4
0
    def __init__(self, window, player):
        super().__init__('search', None, window)

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0

        self._album_widget = AlbumWidget(player)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)
        self._album_widget.bind_property("selected-items-count", self,
                                         "selected-items-count")

        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None

        self._search_mode_active = False
        self.connect("notify::search-state", self._on_search_state_changed)
Beispiel #5
0
    def __init__(self, window, player):
        super().__init__('search', None, window, Gd.MainViewType.LIST)

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._view.get_generic_view().set_show_expanders(False)
        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None
Beispiel #6
0
    def __init__(self, window, player):
        super().__init__('search', None, window)

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self, self._header_bar,
                                         self._selection_toolbar)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None
Beispiel #7
0
    def __init__(self, application, player=None):
        """Initialize AlbumsView

        :param application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        # FIXME: Make these properties.
        self.name = "albums"
        self.title = _("Albums")

        self._window = application.props.window
        self._headerbar = self._window._headerbar

        model = self._window._app.props.coremodel.props.albums_sort
        self._flowbox.bind_model(model, self._create_widget)
        self._flowbox.connect("child-activated", self._on_child_activated)

        self.connect("notify::selection-mode", self._on_selection_mode_changed)

        self.bind_property('selection-mode', self._window, 'selection-mode',
                           GObject.BindingFlags.BIDIRECTIONAL)

        self._album_widget = AlbumWidget(application.props.player, self)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self.connect("notify::search-mode-active",
                     self._on_search_mode_changed)

        self.show_all()
Beispiel #8
0
 def __init__(self, window, player):
     BaseView.__init__(self, 'albums', _("Albums"), window, None)
     self._albumWidget = AlbumWidget(player, self)
     self.player = player
     self.add(self._albumWidget)
     self.albums_selected = []
     self.all_items = []
     self.items_selected = []
     self.items_selected_callback = None
     self._add_list_renderers()
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window)

        self.player = player
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None
Beispiel #10
0
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window, None)

        self._queue = LifoQueue()
        self._album_widget = AlbumWidget(player, self)
        self.player = player
        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None
        self._add_list_renderers()
        self._init = True
Beispiel #11
0
    def __init__(self, window, player):
        self._window = window
        super().__init__('albums', _("Albums"), window)

        self.player = player
        self._album_widget = AlbumWidget(player, self)
        self._album_widget.bind_property(
            "selection-mode", self, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None

        self.connect(
            "notify::search-mode-active", self._on_search_mode_changed)
Beispiel #12
0
    def __init__(self, application):
        """Initialize AlbumsView

        :param application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        self.props.name = "albums"

        self._application = application
        self._window = application.props.window
        self._headerbar = self._window._headerbar
        self._adjustment_timeout_id = 0
        self._viewport = self._scrolled_window.get_child()
        self._widget_counter = 1
        self._ctrl_hold = False

        model = self._application.props.coremodel.props.albums_sort
        self._flowbox.bind_model(model, self._create_widget)
        self._flowbox.set_hadjustment(self._scrolled_window.get_hadjustment())
        self._flowbox.set_vadjustment(self._scrolled_window.get_vadjustment())
        self._flowbox.connect("child-activated", self._on_child_activated)

        self.bind_property("selection-mode", self._window, "selection-mode",
                           GObject.BindingFlags.DEFAULT)

        self._window.connect("notify::selection-mode",
                             self._on_selection_mode_changed)

        self._album_widget = AlbumWidget(self._application)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self.connect("notify::search-mode-active",
                     self._on_search_mode_changed)

        self._scrolled_window.props.vadjustment.connect(
            "value-changed", self._on_vadjustment_changed)
        self._scrolled_window.props.vadjustment.connect(
            "changed", self._on_vadjustment_changed)
Beispiel #13
0
 def __init__(self, window, player):
     BaseView.__init__(self, "albums", _("Albums"), window, None)
     self._albumWidget = AlbumWidget(player, self)
     self.player = player
     self.add(self._albumWidget)
     self.albums_selected = []
     self.all_items = []
     self.items_selected = []
     self.items_selected_callback = None
     self._add_list_renderers()
Beispiel #14
0
    def __init__(self, window, player):
        self._coremodel = window._app.props.coremodel
        self._model = self._coremodel.props.songs_search
        self._album_model = self._coremodel.props.albums_search
        self._artist_model = self._coremodel.props.artists_search
        super().__init__('search', None, window)

        self.player = player

        self.previous_view = None

        self._album_widget = AlbumWidget(player, self)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self._artist_albums_widget = None

        self._search_mode_active = False
Beispiel #15
0
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window)

        self.player = player

        self._album_widget = AlbumWidget(player)
        self._album_widget.bind_property(
            "selection-mode", self, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL)
        self._album_widget.bind_property(
            "selected-items-count", self, "selected-items-count")

        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None

        self.connect(
            "notify::search-mode-active", self._on_search_mode_changed)
Beispiel #16
0
    def __init__(self, window, player):
        BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST)
        self._items = {}
        self.isStarred = None
        self.iter_to_clean = None

        scale = self.get_scale_factor()
        loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
                                                      ArtSize.small)
        self._loading_icon = Gdk.pixbuf_get_from_surface(
            loading_icon_surface,
            0,
            0,
            loading_icon_surface.get_width(),
            loading_icon_surface.get_height())

        self._add_list_renderers()
        self.player = player
        self.head_iters = [None, None, None, None]
        self.songs_model = self.model
        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self.albums_selected = []
        self._albums = {}
        self._albumWidget = AlbumWidget(player, self)
        self.add(self._albumWidget)

        self.artists_albums_selected = []
        self._artists = {}
        self._artistAlbumsWidget = None

        self.view.get_generic_view().set_show_expanders(False)
        self.items_selected = []
        self.items_selected_callback = None

        self.found_items_number = None
Beispiel #17
0
class AlbumsView(BaseView):
    def __repr__(self):
        return '<AlbumsView>'

    @log
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window)

        self.player = player
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None

    @log
    def _on_changes_pending(self, data=None):
        if (self._init and not self.props.selection_mode):
            self._offset = 0
            self.populate()
            grilo.changes_pending['Albums'] = False

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        super()._on_selection_mode_changed(widget, data)

        if (not self.props.selection_mode and grilo.changes_pending['Albums']):
            self._on_changes_pending()

    @log
    def _setup_view(self):
        self._view = Gtk.FlowBox(homogeneous=True,
                                 hexpand=True,
                                 halign=Gtk.Align.FILL,
                                 valign=Gtk.Align.START,
                                 selection_mode=Gtk.SelectionMode.NONE,
                                 margin=18,
                                 row_spacing=12,
                                 column_spacing=6,
                                 min_children_per_line=1,
                                 max_children_per_line=20)

        self._view.get_style_context().add_class('content-view')
        self._view.connect('child-activated', self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self._view)
        scrolledwin.show()

        self._box.add(scrolledwin)

    @log
    def _back_button_clicked(self, widget, data=None):
        self._headerbar.state = HeaderBar.State.MAIN
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        if self.props.selection_mode:
            return

        item = child.props.media
        # Update and display the album widget if not in selection mode
        self._album_widget.update(item)

        self._headerbar.props.state = HeaderBar.State.CHILD
        self._headerbar.props.title = utils.get_album_title(item)
        self._headerbar.props.subtitle = utils.get_artist_name(item)
        self.set_visible_child(self._album_widget)

    @log
    def populate(self):
        self._window.notifications_popup.push_loading()
        grilo.populate_albums(self._offset, self._add_item)
        self._init = True

    @log
    def get_selected_songs(self, callback):
        # FIXME: we call into private objects with full knowledge of
        # what is there
        if self._headerbar.props.state == HeaderBar.State.CHILD:
            callback(self._album_widget._disc_listbox.get_selected_items())
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self.albums_index = 0
            if len(self.albums_selected):
                self._get_selected_album_songs()

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if item:
            # Store all items to optimize 'Select All' action
            self.all_items.append(item)

            # Add to the flowbox
            child = self._create_album_item(item)
            self._view.add(child)
            self._offset += 1
        elif remaining == 0:
            self._view.show()
            self._window.notifications_popup.pop_loading()
            self._init = False

    def _create_album_item(self, item):
        child = AlbumCover(item)

        child.connect('notify::selected', self._on_selection_changed)

        self.bind_property('selection-mode', child, 'selection-mode',
                           GObject.BindingFlags.BIDIRECTIONAL)

        return child

    @log
    def _on_selection_changed(self, child, data=None):
        if (child.props.selected
                and child.props.media not in self.albums_selected):
            self.albums_selected.append(child.props.media)
        elif (not child.props.selected
              and child.props.media in self.albums_selected):
            self.albums_selected.remove(child.props.media)

        self.props.selected_items_count = len(self.albums_selected)

    @log
    def _get_selected_album_songs(self):
        grilo.populate_album_songs(self.albums_selected[self.albums_index],
                                   self._add_selected_item)
        self.albums_index += 1

    @log
    def _add_selected_item(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < self.props.selected_items_count:
                self._get_selected_album_songs()
            else:
                self.items_selected_callback(self.items_selected)

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        for child in self._view.get_children():
            child.props.selected = selected

    @log
    def select_all(self):
        self.albums_selected = list(self.all_items)
        self._toggle_all_selection(True)

    @log
    def unselect_all(self):
        self.albums_selected = []
        self._toggle_all_selection(False)
Beispiel #18
0
class AlbumsView(BaseView):
    def __repr__(self):
        return "<AlbumsView>"

    @log
    def __init__(self, window, player):
        BaseView.__init__(self, "albums", _("Albums"), window, None)
        self._albumWidget = AlbumWidget(player, self)
        self.player = player
        self.add(self._albumWidget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None
        self._add_list_renderers()

    @log
    def _on_changes_pending(self, data=None):
        if self._init and not self.header_bar._selectionMode:
            self._offset = 0
            self._init = True
            GLib.idle_add(self.populate)
            grilo.changes_pending["Albums"] = False

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if not self.header_bar._selectionMode and grilo.changes_pending["Albums"]:
            self._on_changes_pending()

    @log
    def _setup_view(self, view_type):
        self.view = Gtk.FlowBox(
            homogeneous=True,
            hexpand=True,
            halign=Gtk.Align.FILL,
            valign=Gtk.Align.START,
            selection_mode=Gtk.SelectionMode.NONE,
            margin=18,
            row_spacing=12,
            column_spacing=6,
            min_children_per_line=1,
            max_children_per_line=25,
        )

        self.view.connect("child-activated", self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self.view)
        scrolledwin.show()

        self._box.add(scrolledwin)

    @log
    def _back_button_clicked(self, widget, data=None):
        self.header_bar.reset_header_title()
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        item = child.media_item

        if self.star_handler.star_renderer_click:
            self.star_handler.star_renderer_click = False
            return

        # Toggle the selection when in selection mode
        if self.selection_mode:
            child.check.set_active(not child.check.get_active())
            return

        title = utils.get_media_title(item)
        self._escaped_title = title
        self._artist = utils.get_artist_name(item)

        self._albumWidget.update(self._artist, title, item, self.header_bar, self.selection_toolbar)

        self.header_bar.set_state(ToolbarState.CHILD_VIEW)
        self.header_bar.header_bar.set_title(self._escaped_title)
        self.header_bar.header_bar.sub_title = self._artist
        self.set_visible_child(self._albumWidget)

    @log
    def update_title(self):
        self.header_bar.header_bar.set_title(self._escaped_title)
        self.header_bar.header_bar.sub_title = self._artist

    @log
    def populate(self):
        self.window.push_loading_notification()
        grilo.populate_albums(self._offset, self._add_item)

    @log
    def get_selected_tracks(self, callback):
        # FIXME: we call into private objects with full knowledge of
        # what is there
        if self.header_bar._state == ToolbarState.CHILD_VIEW:
            callback(self._albumWidget._disc_listbox.get_selected_items())
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self.albums_index = 0
            if len(self.albums_selected):
                self._get_selected_album_songs()

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if item:
            # Store all items to optimize 'Select All' action
            self.all_items.append(item)

            # Add to the flowbox
            child = self._create_album_item(item)
            self.view.add(child)
        elif remaining == 0:
            self.window.pop_loading_notification()
            self.view.show()

    def _create_album_item(self, item):
        artist = utils.get_artist_name(item)
        title = utils.get_media_title(item)

        builder = Gtk.Builder.new_from_resource("/org/gnome/Music/AlbumCover.ui")

        child = Gtk.FlowBoxChild()
        child.image = builder.get_object("image")
        child.check = builder.get_object("check")
        child.title = builder.get_object("title")
        child.subtitle = builder.get_object("subtitle")
        child.events = builder.get_object("events")
        child.media_item = item

        child.title.set_label(title)
        child.subtitle.set_label(artist)

        child.image.set_from_surface(self._loading_icon_surface)
        # In the case of off-sized icons (eg. provided in the soundfile)
        # keep the size request equal to all other icons to get proper
        # alignment with GtkFlowBox.
        child.image.set_property("width-request", ArtSize.medium.width)
        child.image.set_property("height-request", ArtSize.medium.height)

        child.events.connect("button-release-event", self._on_album_event_triggered, child)

        child.check_handler_id = child.check.connect("notify::active", self._on_child_toggled, child)

        child.check.bind_property("visible", self, "selection_mode", GObject.BindingFlags.BIDIRECTIONAL)

        child.add(builder.get_object("main_box"))
        child.show()

        self.cache.lookup(item, ArtSize.medium, self._on_lookup_ready, child)

        return child

    @log
    def _on_album_event_triggered(self, evbox, event, child):
        if event.button is 3:
            self._on_selection_mode_request()
            if self.selection_mode:
                child.check.set_active(True)

    def _on_lookup_ready(self, icon, child):
        child.image.set_from_surface(icon)

    @log
    def _on_child_toggled(self, check, pspec, child):
        if check.get_active() and not child.media_item in self.albums_selected:
            self.albums_selected.append(child.media_item)
        elif not check.get_active() and child.media_item in self.albums_selected:
            self.albums_selected.remove(child.media_item)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def _get_selected_album_songs(self):
        grilo.populate_album_songs(self.albums_selected[self.albums_index], self._add_selected_item)
        self.albums_index += 1

    @log
    def _add_selected_item(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < len(self.albums_selected):
                self._get_selected_album_songs()
            else:
                self.items_selected_callback(self.items_selected)

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        for child in self.view.get_children():
            GObject.signal_handler_block(child.check, child.check_handler_id)

            # Set the checkbutton state without emiting the signal
            child.check.set_active(selected)

            GObject.signal_handler_unblock(child.check, child.check_handler_id)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def select_all(self):
        self.albums_selected = list(self.all_items)
        self._toggle_all_selection(True)

    @log
    def unselect_all(self):
        self.albums_selected = []
        self._toggle_all_selection(False)
Beispiel #19
0
class AlbumsView(BaseView):
    def __repr__(self):
        return '<AlbumsView>'

    @log
    def __init__(self, window, player):
        BaseView.__init__(self, 'albums', _("Albums"), window, None)
        self._albumWidget = AlbumWidget(player, self)
        self.player = player
        self.add(self._albumWidget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None
        self._add_list_renderers()

    @log
    def _on_changes_pending(self, data=None):
        if (self._init and not self._header_bar._selectionMode):
            self._offset = 0
            self._init = True
            GLib.idle_add(self.populate)
            grilo.changes_pending['Albums'] = False

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if (not self._header_bar._selectionMode
                and grilo.changes_pending['Albums']):
            self._on_changes_pending()

    @log
    def _setup_view(self, view_type):
        self._view = Gtk.FlowBox(homogeneous=True,
                                 hexpand=True,
                                 halign=Gtk.Align.FILL,
                                 valign=Gtk.Align.START,
                                 selection_mode=Gtk.SelectionMode.NONE,
                                 margin=18,
                                 row_spacing=12,
                                 column_spacing=6,
                                 min_children_per_line=1,
                                 max_children_per_line=25)

        self._view.connect('child-activated', self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self._view)
        scrolledwin.show()

        self._box.add(scrolledwin)

    @log
    def _back_button_clicked(self, widget, data=None):
        self._header_bar.reset_header_title()
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        item = child.media_item

        if self._star_handler.star_renderer_click:
            self._star_handler.star_renderer_click = False
            return

        # Toggle the selection when in selection mode
        if self.selection_mode:
            child.check.set_active(not child.check.get_active())
            return

        title = utils.get_media_title(item)
        self._escaped_title = title
        self._artist = utils.get_artist_name(item)

        self._albumWidget.update(self._artist, title, item, self._header_bar,
                                 self._selection_toolbar)

        self._header_bar.set_state(ToolbarState.CHILD_VIEW)
        self._header_bar.header_bar.set_title(self._escaped_title)
        self._header_bar.header_bar.sub_title = self._artist
        self.set_visible_child(self._albumWidget)

    @log
    def update_title(self):
        self._header_bar.header_bar.set_title(self._escaped_title)
        self._header_bar.header_bar.sub_title = self._artist

    @log
    def populate(self):
        self._window.push_loading_notification()
        grilo.populate_albums(self._offset, self._add_item)

    @log
    def get_selected_songs(self, callback):
        # FIXME: we call into private objects with full knowledge of
        # what is there
        if self._header_bar._state == ToolbarState.CHILD_VIEW:
            callback(self._albumWidget._disc_listbox.get_selected_items())
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self.albums_index = 0
            if len(self.albums_selected):
                self._get_selected_album_songs()

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if item:
            # Store all items to optimize 'Select All' action
            self.all_items.append(item)

            # Add to the flowbox
            child = self._create_album_item(item)
            self._view.add(child)
        elif remaining == 0:
            self._window.pop_loading_notification()
            self._view.show()

    def _create_album_item(self, item):
        artist = utils.get_artist_name(item)
        title = utils.get_media_title(item)

        builder = Gtk.Builder.new_from_resource(
            '/org/gnome/Music/AlbumCover.ui')

        child = Gtk.FlowBoxChild()
        child.image = builder.get_object('image')
        child.check = builder.get_object('check')
        child.title = builder.get_object('title')
        child.subtitle = builder.get_object('subtitle')
        child.events = builder.get_object('events')
        child.media_item = item

        child.title.set_label(title)
        child.subtitle.set_label(artist)

        child.image.set_from_surface(self._loading_icon_surface)
        # In the case of off-sized icons (eg. provided in the soundfile)
        # keep the size request equal to all other icons to get proper
        # alignment with GtkFlowBox.
        child.image.set_property("width-request", ArtSize.medium.width)
        child.image.set_property("height-request", ArtSize.medium.height)

        child.events.connect('button-release-event',
                             self._on_album_event_triggered, child)

        child.check_handler_id = child.check.connect('notify::active',
                                                     self._on_child_toggled,
                                                     child)

        child.check.bind_property('visible', self, 'selection_mode',
                                  GObject.BindingFlags.BIDIRECTIONAL)

        child.add(builder.get_object('main_box'))
        child.show()

        self._cache.lookup(item, ArtSize.medium, self._on_lookup_ready, child)

        return child

    @log
    def _on_album_event_triggered(self, evbox, event, child):
        if event.button is 3:
            self._on_selection_mode_request()
            if self.selection_mode:
                child.check.set_active(True)

    def _on_lookup_ready(self, icon, child):
        child.image.set_from_surface(icon)

    @log
    def _on_child_toggled(self, check, pspec, child):
        if check.get_active() and not child.media_item in self.albums_selected:
            self.albums_selected.append(child.media_item)
        elif not check.get_active(
        ) and child.media_item in self.albums_selected:
            self.albums_selected.remove(child.media_item)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def _get_selected_album_songs(self):
        grilo.populate_album_songs(self.albums_selected[self.albums_index],
                                   self._add_selected_item)
        self.albums_index += 1

    @log
    def _add_selected_item(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < len(self.albums_selected):
                self._get_selected_album_songs()
            else:
                self.items_selected_callback(self.items_selected)

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        for child in self._view.get_children():
            GObject.signal_handler_block(child.check, child.check_handler_id)

            # Set the checkbutton state without emiting the signal
            child.check.set_active(selected)

            GObject.signal_handler_unblock(child.check, child.check_handler_id)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def select_all(self):
        self.albums_selected = list(self.all_items)
        self._toggle_all_selection(True)

    @log
    def unselect_all(self):
        self.albums_selected = []
        self._toggle_all_selection(False)
Beispiel #20
0
class SearchView(BaseView):
    __gsignals__ = {
        'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ())
    }

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

    @log
    def __init__(self, window, player):
        BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST)
        self._items = {}
        self.isStarred = None
        self.iter_to_clean = None

        scale = self.get_scale_factor()
        loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
                                                      ArtSize.small)
        self._loading_icon = Gdk.pixbuf_get_from_surface(
            loading_icon_surface,
            0,
            0,
            loading_icon_surface.get_width(),
            loading_icon_surface.get_height())

        self._add_list_renderers()
        self.player = player
        self.head_iters = [None, None, None, None]
        self.songs_model = self.model
        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self.albums_selected = []
        self._albums = {}
        self._albumWidget = AlbumWidget(player, self)
        self.add(self._albumWidget)

        self.artists_albums_selected = []
        self._artists = {}
        self._artistAlbumsWidget = None

        self.view.get_generic_view().set_show_expanders(False)
        self.items_selected = []
        self.items_selected_callback = None

        self.found_items_number = None

    @log
    def _no_music_found_callback(self, view):
        self.window._stack.set_visible_child_name('emptysearch')
        emptysearch = self.window._stack.get_child_by_name('emptysearch')
        emptysearch._artistAlbumsWidget = self._artistAlbumsWidget

    @log
    def _back_button_clicked(self, widget, data=None):
        self.header_bar.searchbar.show_bar(True, False)
        if self.get_visible_child() == self._artistAlbumsWidget:
            self._artistAlbumsWidget.destroy()
            self._artistAlbumsWidget = None
        elif self.get_visible_child() == self._grid:
            self.window.views[0].set_visible_child(self.window.views[0]._grid)
        self.set_visible_child(self._grid)
        self.window.toolbar.set_state(ToolbarState.MAIN)

    @log
    def _on_item_activated(self, widget, id, path):
        if self.star_handler.star_renderer_click:
            self.star_handler.star_renderer_click = False
            return

        try:
            child_path = self.filter_model.convert_path_to_child_path(path)
        except TypeError:
            return
        _iter = self.model.get_iter(child_path)
        if self.model[_iter][11] == 'album':
            title = self.model.get_value(_iter, 2)
            artist = self.model.get_value(_iter, 3)
            item = self.model.get_value(_iter, 5)
            self._albumWidget.update(artist, title, item,
                                     self.header_bar, self.selection_toolbar)
            self.header_bar.set_state(ToolbarState.SEARCH_VIEW)
            title = utils.get_media_title(item)
            self.header_bar.header_bar.set_title(title)
            self.header_bar.header_bar.sub_title = artist
            self.set_visible_child(self._albumWidget)
            self.header_bar.searchbar.show_bar(False)
        elif self.model[_iter][11] == 'artist':
            artist = self.model.get_value(_iter, 2)
            albums = self._artists[artist.casefold()]['albums']

            self._artistAlbumsWidget = ArtistAlbumsWidget(
                artist, albums, self.player,
                self.header_bar, self.selection_toolbar, self.window, True
            )
            self.add(self._artistAlbumsWidget)
            self._artistAlbumsWidget.show()

            self.header_bar.set_state(ToolbarState.SEARCH_VIEW)
            self.header_bar.header_bar.set_title(artist)
            self.set_visible_child(self._artistAlbumsWidget)
            self.header_bar.searchbar.show_bar(False)
        elif self.model[_iter][11] == 'song':
            if self.model.get_value(_iter, 12) != DiscoveryStatus.FAILED:
                child_iter = self.songs_model.convert_child_iter_to_iter(_iter)[1]
                self.player.set_playlist('Search Results', None, self.songs_model, child_iter, 5, 12)
                self.player.set_playing(True)
        else:  # Headers
            if self.view.get_generic_view().row_expanded(path):
                self.view.get_generic_view().collapse_row(path)
            else:
                self.view.get_generic_view().expand_row(path, False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self._artistAlbumsWidget is not None and self.get_visible_child() == self._artistAlbumsWidget:
            self._artistAlbumsWidget.set_selection_mode(self.header_bar._selectionMode)

    @log
    def _add_search_item(self, source, param, item, remaining=0, data=None):
        if not item:
            if grilo._search_callback_counter == 0 and grilo.search_source:
                self.emit('no-music-found')
            return

        if data != self.model:
            return

        artist = utils.get_artist_name(item)
        album = utils.get_album_title(item)
        composer = item.get_composer()

        key = '%s-%s' % (artist, album)
        if key not in self._albums:
            self._albums[key] = Grl.Media()
            self._albums[key].set_title(album)
            self._albums[key].add_artist(artist)
            self._albums[key].set_composer(composer)
            self._albums[key].set_source(source.get_id())
            self._albums[key].tracks = []
            self._add_item(source, None, self._albums[key], 0, [self.model, 'album'])
            self._add_item(source, None, self._albums[key], 0, [self.model, 'artist'])

        self._albums[key].tracks.append(item)
        self._add_item(source, None, item, 0, [self.model, 'song'])

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if data is None:
            return

        model, category = data

        self.found_items_number = (
            self.model.iter_n_children(self.head_iters[0]) +
            self.model.iter_n_children(self.head_iters[1]) +
            self.model.iter_n_children(self.head_iters[2]) +
            self.model.iter_n_children(self.head_iters[3]))

        if category == 'song' and self.found_items_number == 0 and remaining == 0:
            if grilo.search_source:
                self.emit('no-music-found')

        # We need to remember the view before the search view
        if self.window.curr_view != self.window.views[5] and \
           self.window.prev_view != self.window.views[5]:
            self.previous_view = self.window.prev_view

        if remaining == 0:
            self.window.pop_loading_notification()
            self.view.show()

        if not item or model != self.model:
            return

        self._offset += 1
        title = utils.get_media_title(item)
        item.set_title(title)
        artist = utils.get_artist_name(item)
        # FIXME: Can't be None in treemodel
        composer = item.get_composer() or ""

        group = 3
        try:
            group = {'album': 0, 'artist': 1, 'song': 2}[category]
        except:
            pass

        # FIXME: HiDPI icon lookups return a surface that can't be
        # scaled by GdkPixbuf, so it results in a * scale factor sized
        # icon for the search view.
        _iter = None
        if category == 'album':
            _iter = self.model.insert_with_values(
                self.head_iters[group], -1,
                [0, 2, 3, 4, 5, 9, 11, 13],
                [str(item.get_id()), title, artist,
                 self._loading_icon, item, 2, category, composer])
            self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter)
        elif category == 'song':
            _iter = self.model.insert_with_values(
                self.head_iters[group], -1,
                [0, 2, 3, 4, 5, 9, 11, 13],
                [str(item.get_id()), title, artist,
                 self._loading_icon, item,
                 2 if source.get_id() != 'grl-tracker-source' \
                    else item.get_favourite(), category, composer])
            self.cache.lookup(item, ArtSize.small, self._on_lookup_ready, _iter)
        else:
            if not artist.casefold() in self._artists:
                _iter = self.model.insert_with_values(
                    self.head_iters[group], -1,
                    [0, 2, 4, 5, 9, 11, 13],
                    [str(item.get_id()), artist,
                     self._loading_icon, item, 2, category, composer])
                self.cache.lookup(item, ArtSize.small, self._on_lookup_ready,
                                  _iter)
                self._artists[artist.casefold()] = {'iter': _iter, 'albums': []}

            self._artists[artist.casefold()]['albums'].append(item)

        if self.model.iter_n_children(self.head_iters[group]) == 1:
            path = self.model.get_path(self.head_iters[group])
            path = self.filter_model.convert_child_path_to_path(path)
            self.view.get_generic_view().expand_row(path, False)

    @log
    def _add_list_renderers(self):
        list_widget = self.view.get_generic_view()
        list_widget.set_halign(Gtk.Align.CENTER)
        list_widget.set_size_request(530, -1)
        cols = list_widget.get_columns()

        title_renderer = Gtk.CellRendererText(
            xpad=12,
            xalign=0.0,
            yalign=0.5,
            height=32,
            ellipsize=Pango.EllipsizeMode.END,
            weight=Pango.Weight.BOLD
        )
        list_widget.add_renderer(title_renderer,
                                 self._on_list_widget_title_render, None)
        cols[0].add_attribute(title_renderer, 'text', 2)

        self.star_handler.add_star_renderers(list_widget, cols, hidden=False)

        cells = cols[0].get_cells()
        cols[0].reorder(cells[0], -1)
        cols[0].set_cell_data_func(cells[0], self._on_list_widget_selection_render, None)

    def _on_list_widget_selection_render(self, col, cell, model, _iter, data):
        cell.set_visible(self.view.get_selection_mode() and model.iter_parent(_iter) is not None)

    def _on_list_widget_title_render(self, col, cell, model, _iter, data):
        cells = col.get_cells()
        cells[0].set_visible(model.iter_parent(_iter) is not None)
        cells[1].set_visible(model.iter_parent(_iter) is not None)
        cells[2].set_visible(model.iter_parent(_iter) is None)

    @log
    def populate(self):
        self._init = True
        self.window.push_loading_notification()
        self.header_bar.set_state(ToolbarState.MAIN)

    @log
    def get_selected_tracks(self, callback):
        if self.get_visible_child() == self._albumWidget:
            callback(self._albumWidget.view.get_selected_items())
        elif self.get_visible_child() == self._artistAlbumsWidget:
            items = []
            # FIXME: calling into private model
            for row in self._artistAlbumsWidget._model:
                if row[6]:
                    items.append(row[5])
            callback(items)
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self._get_selected_albums()

    @log
    def _get_selected_albums(self):
        self.albums_index = 0
        self.albums_selected = [self.model[child_path][5]
                                for child_path in [self.filter_model.convert_path_to_child_path(path)
                                                   for path in self.view.get_selection()]
                                if self.model[child_path][11] == 'album']
        if len(self.albums_selected):
            self._get_selected_albums_songs()
        else:
            self._get_selected_artists()

    @log
    def _get_selected_albums_songs(self):
        grilo.populate_album_songs(
            self.albums_selected[self.albums_index],
            self._add_selected_albums_songs)
        self.albums_index += 1

    @log
    def _add_selected_albums_songs(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < len(self.albums_selected):
                self._get_selected_albums_songs()
            else:
                self._get_selected_artists()

    @log
    def _get_selected_artists(self):
        self.artists_albums_index = 0
        self.artists_selected = [self._artists[self.model[child_path][2].casefold()]
                                 for child_path in [self.filter_model.convert_path_to_child_path(path)
                                                    for path in self.view.get_selection()]
                                 if self.model[child_path][11] == 'artist']

        self.artists_albums_selected = []
        for artist in self.artists_selected:
            self.artists_albums_selected.extend(artist['albums'])

        if len(self.artists_albums_selected):
            self._get_selected_artists_albums_songs()
        else:
            self._get_selected_songs()

    @log
    def _get_selected_artists_albums_songs(self):
        grilo.populate_album_songs(
            self.artists_albums_selected[self.artists_albums_index],
            self._add_selected_artists_albums_songs)
        self.artists_albums_index += 1

    @log
    def _add_selected_artists_albums_songs(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.artists_albums_index < len(self.artists_albums_selected):
                self._get_selected_artists_albums_songs()
            else:
                self._get_selected_songs()

    @log
    def _get_selected_songs(self):
        self.items_selected.extend([self.model[child_path][5]
                                    for child_path in [self.filter_model.convert_path_to_child_path(path)
                                                       for path in self.view.get_selection()]
                                    if self.model[child_path][11] == 'song'])
        self.items_selected_callback(self.items_selected)

    @log
    def _filter_visible_func(self, model, _iter, data=None):
        return model.iter_parent(_iter) is not None or model.iter_has_child(_iter)

    @log
    def set_search_text(self, search_term, fields_filter):
        query_matcher = {
            'album': {
                'search_all': Query.get_albums_with_any_match,
                'search_artist': Query.get_albums_with_artist_match,
                'search_composer': Query.get_albums_with_composer_match,
                'search_album': Query.get_albums_with_album_match,
                'search_track': Query.get_albums_with_track_match,
            },
            'artist': {
                'search_all': Query.get_artists_with_any_match,
                'search_artist': Query.get_artists_with_artist_match,
                'search_composer': Query.get_artists_with_composer_match,
                'search_album': Query.get_artists_with_album_match,
                'search_track': Query.get_artists_with_track_match,
            },
            'song': {
                'search_all': Query.get_songs_with_any_match,
                'search_artist': Query.get_songs_with_artist_match,
                'search_composer': Query.get_songs_with_composer_match,
                'search_album': Query.get_songs_with_album_match,
                'search_track': Query.get_songs_with_track_match,
            },
        }

        self.model = Gtk.TreeStore(
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,    # item title or header text
            GObject.TYPE_STRING,    # artist for albums and songs
            GdkPixbuf.Pixbuf,       # album art
            GObject.TYPE_OBJECT,    # item
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,
            GObject.TYPE_STRING,
            GObject.TYPE_INT,
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_STRING,    # type
            GObject.TYPE_INT,
            GObject.TYPE_STRING,    # composer
        )
        self.filter_model = self.model.filter_new(None)
        self.filter_model.set_visible_func(self._filter_visible_func)
        self.view.set_model(self.filter_model)

        self._albums = {}
        self._artists = {}

        if search_term == "":
            return

        albums_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Albums"), 2])
        artists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Artists"), 2])
        songs_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Songs"), 2])
        playlists_iter = self.model.insert_with_values(None, -1, [2, 9], [_("Playlists"), 2])

        self.head_iters = [albums_iter, artists_iter, songs_iter, playlists_iter]
        self.songs_model = self.model.filter_new(self.model.get_path(songs_iter))

        # Use queries for Tracker
        if not grilo.search_source or \
           grilo.search_source.get_id() == 'grl-tracker-source':
            for category in ('album', 'artist', 'song'):
                query = query_matcher[category][fields_filter](search_term)
                grilo.populate_custom_query(query, self._add_item, -1, [self.model, category])
        if not grilo.search_source or \
           grilo.search_source.get_id() != 'grl-tracker-source':
            # nope, can't do - reverting to Search
            grilo.search(search_term, self._add_search_item, self.model)
Beispiel #21
0
class SearchView(Gtk.Stack):
    """Gridlike view of search results.

    Three sections: artists, albums, songs.
    """

    __gtype_name__ = "SearchView"

    class State(IntEnum):
        """The different states of SearchView
        """
        MAIN = 0
        ALL_ALBUMS = 1
        ALL_ARTISTS = 2
        ALBUM = 3
        ARTIST = 4

    search_state = GObject.Property(type=int, default=Search.State.NONE)
    selection_mode = GObject.Property(type=bool, default=False)
    state = GObject.Property(type=int, default=State.MAIN)
    title = GObject.Property(type=str,
                             default="",
                             flags=GObject.ParamFlags.READABLE)

    _album_header = Gtk.Template.Child()
    _album_flowbox = Gtk.Template.Child()
    _album_all_flowbox = Gtk.Template.Child()
    _all_search_results = Gtk.Template.Child()
    _artist_header = Gtk.Template.Child()
    _artist_all_flowbox = Gtk.Template.Child()
    _artist_flowbox = Gtk.Template.Child()
    _search_results = Gtk.Template.Child()
    _songs_header = Gtk.Template.Child()
    _songs_listbox = Gtk.Template.Child()
    _view_all_albums = Gtk.Template.Child()
    _view_all_artists = Gtk.Template.Child()

    def __init__(self, application):
        """Initialize SearchView

        :param GtkApplication application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        self.props.name = "search"

        self._application = application
        self._coremodel = application.props.coremodel
        self._model = self._coremodel.props.songs_search
        self._album_model = self._coremodel.props.albums_search
        self._album_filter = self._coremodel.props.albums_search_filter
        self._album_filter.set_filter_func(self._core_filter,
                                           self._album_model, 12)

        self._artist_model = self._coremodel.props.artists_search
        self._artist_filter = self._coremodel.props.artists_search_filter
        self._artist_filter.set_filter_func(self._core_filter,
                                            self._artist_model, 6)

        self._model.connect_after("items-changed",
                                  self._on_model_items_changed)
        self._songs_listbox.bind_model(self._model, self._create_song_widget)
        self._on_model_items_changed(self._model, 0, 0, 0)

        self._album_filter.connect_after("items-changed",
                                         self._on_album_model_items_changed)
        self._album_flowbox.bind_model(self._album_filter,
                                       self._create_album_widget)
        self._album_flowbox.connect("size-allocate",
                                    self._on_album_flowbox_size_allocate)
        self._on_album_model_items_changed(self._album_filter, 0, 0, 0)

        self._artist_filter.connect_after("items-changed",
                                          self._on_artist_model_items_changed)
        self._artist_flowbox.bind_model(self._artist_filter,
                                        self._create_artist_widget)
        self._artist_flowbox.connect("size-allocate",
                                     self._on_artist_flowbox_size_allocate)
        self._on_artist_model_items_changed(self._artist_filter, 0, 0, 0)

        self._player = self._application.props.player

        self._window = application.props.window
        self._headerbar = self._window._headerbar

        self.connect("notify::selection-mode", self._on_selection_mode_changed)

        self.bind_property('selection-mode', self._window, 'selection-mode',
                           GObject.BindingFlags.BIDIRECTIONAL)

        self._album_widget = AlbumWidget(self._application)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self._scrolled_artist_window = None

        self._search_mode_active = False

    def _core_filter(self, coreitem, coremodel, nr_items):
        if coremodel.get_n_items() <= 5:
            return True

        for i in range(nr_items):
            if coremodel.get_item(i) == coreitem:
                return True

        return False

    def _create_song_widget(self, coresong):
        song_widget = SongWidget(coresong, False, True)
        song_widget.props.show_song_number = False

        self.bind_property(
            "selection-mode", song_widget, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        song_widget.connect('button-release-event', self._song_activated)

        return song_widget

    def _create_album_widget(self, corealbum):
        album_widget = AlbumCover(corealbum)
        album_widget.retrieve()

        self.bind_property(
            "selection-mode", album_widget, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        # NOTE: Adding SYNC_CREATE here will trigger all the nested
        # models to be created. This will slow down initial start,
        # but will improve initial 'select all' speed.
        album_widget.bind_property("selected", corealbum, "selected",
                                   GObject.BindingFlags.BIDIRECTIONAL)

        return album_widget

    def _create_artist_widget(self, coreartist):
        artist_tile = ArtistSearchTile(coreartist)

        self.bind_property(
            "selection-mode", artist_tile, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        return artist_tile

    def _on_album_model_items_changed(self, model, position, removed, added):
        items_found = model.get_n_items() > 0
        self._album_header.props.visible = items_found
        self._album_flowbox.props.visible = items_found
        self._check_visibility()

        nr_albums = self._album_model.get_n_items()
        self._view_all_albums.props.visible = (nr_albums > model.get_n_items())

        def set_child_visible(child):
            child.props.visible = True

        self._album_flowbox.foreach(set_child_visible)

    def _on_artist_model_items_changed(self, model, position, removed, added):
        items_found = model.get_n_items() > 0
        self._artist_header.props.visible = items_found
        self._artist_flowbox.props.visible = items_found
        self._check_visibility()

        nr_artists = self._artist_model.get_n_items()
        self._view_all_artists.props.visible = (nr_artists >
                                                model.get_n_items())

        def set_child_visible(child):
            child.props.visible = True

        self._artist_flowbox.foreach(set_child_visible)

    def _on_model_items_changed(self, model, position, removed, added):
        items_found = model.get_n_items() > 0
        self._songs_header.props.visible = items_found
        self._songs_listbox.props.visible = items_found
        self._check_visibility()

    def _check_visibility(self):
        if not self.props.search_mode_active:
            return

        items_found = (self._model.get_n_items() > 0
                       or self._artist_model.get_n_items() > 0
                       or self._album_model.get_n_items() > 0)
        if items_found:
            self.props.search_state = Search.State.RESULT
        else:
            self.props.search_state = Search.State.NO_RESULT

    def _song_activated(self, widget, event):
        if widget.props.select_click:
            widget.props.select_click = False
            return

        mod_mask = Gtk.accelerator_get_default_mod_mask()
        if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK
                and not self.props.selection_mode):
            self.props.selection_mode = True
            return

        if self.props.selection_mode:
            widget.props.select_click = True
            widget.props.selected = not widget.props.selected
            widget.props.coresong.props.selected = widget.props.selected
            return

        (_, button) = event.get_button()
        if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode):
            self._coremodel.props.active_core_object = widget.props.coresong
            self._player.play(widget.props.coresong)

        return True

    def _on_album_flowbox_size_allocate(self, widget, allocation, data=None):
        nb_children = self._album_filter.get_n_items()
        if nb_children == 0:
            return

        first_child = self._album_flowbox.get_child_at_index(0)
        child_height = first_child.get_allocation().height
        if allocation.height > 2.5 * child_height:
            for i in range(nb_children - 1, -1, -1):
                child = self._album_flowbox.get_child_at_index(i)
                if child.props.visible is True:
                    child.props.visible = False
                    return

        children_hidden = False
        for idx in range(nb_children):
            child = self._album_flowbox.get_child_at_index(idx)
            if not child.props.visible:
                children_hidden = True
                break
        if children_hidden is False:
            return

        last_visible_child = self._album_flowbox.get_child_at_index(idx - 1)
        first_row_last = self._album_flowbox.get_child_at_index((idx - 1) // 2)
        second_row_pos = last_visible_child.get_allocation().x
        first_row_pos = first_row_last.get_allocation().x
        child_width = last_visible_child.get_allocation().width
        nb_children_to_add = (first_row_pos - second_row_pos) // child_width
        nb_children_to_add = min(nb_children_to_add + idx, nb_children)
        for i in range(idx, nb_children_to_add):
            child = self._album_flowbox.get_child_at_index(i)
            child.props.visible = True

    def _on_artist_flowbox_size_allocate(self, widget, allocation, data=None):
        nb_children = self._artist_filter.get_n_items()
        if nb_children == 0:
            return

        first_child = self._artist_flowbox.get_child_at_index(0)
        # FIXME: It looks like it is possible that the widget is not
        # yet created, resulting in a crash with first_child being
        # None.
        # Look for a cleaner solution.
        if first_child is None:
            return

        child_height = first_child.get_allocation().height
        if allocation.height > 1.5 * child_height:
            for i in range(nb_children - 1, -1, -1):
                child = self._artist_flowbox.get_child_at_index(i)
                if child.props.visible is True:
                    child.props.visible = False
                    return

        children_hidden = False
        for idx in range(nb_children):
            child = self._artist_flowbox.get_child_at_index(idx)
            if not child.props.visible:
                children_hidden = True
                break
        if children_hidden is False:
            return

        last_child = self._artist_flowbox.get_child_at_index(idx - 1)
        last_child_allocation = last_child.get_allocation()
        child_width = last_child_allocation.width
        if (last_child_allocation.x + 2 * child_width) < allocation.width:
            child = self._artist_flowbox.get_child_at_index(idx)
            child.props.visible = True

    @Gtk.Template.Callback()
    def _on_album_activated(self, widget, child, user_data=None):
        corealbum = child.props.corealbum
        if self.props.selection_mode:
            corealbum.props.selected = not corealbum.props.selected
            return

        # Update and display the album widget if not in selection mode
        self._album_widget.update(corealbum)

        self.props.state = SearchView.State.ALBUM
        self._headerbar.props.state = HeaderBar.State.SEARCH
        self._headerbar.props.title = corealbum.props.title
        self._headerbar.props.subtitle = corealbum.props.artist

        self.set_visible_child(self._album_widget)
        self.props.search_mode_active = False

    @Gtk.Template.Callback()
    def _on_artist_activated(self, widget, child, user_data=None):
        coreartist = child.props.coreartist
        if self.props.selection_mode:
            return

        artist_albums_widget = ArtistAlbumsWidget(coreartist,
                                                  self._application)
        # FIXME: Recreating a view here. Alternate solution is used
        # in AlbumsView: one view created and an update function.
        # Settle on one design.
        self._scrolled_artist_window = Gtk.ScrolledWindow()
        self._scrolled_artist_window.add(artist_albums_widget)
        self._scrolled_artist_window.props.visible = True
        self.add(self._scrolled_artist_window)
        artist_albums_widget.show()

        self.bind_property("selection-mode", artist_albums_widget,
                           "selection-mode",
                           GObject.BindingFlags.BIDIRECTIONAL)

        self.props.state = SearchView.State.ARTIST
        self._headerbar.props.state = HeaderBar.State.SEARCH
        self._headerbar.props.title = coreartist.props.artist
        self._headerbar.props.subtitle = None

        self.set_visible_child(self._scrolled_artist_window)
        self.props.search_mode_active = False

    @Gtk.Template.Callback()
    def _on_all_artists_clicked(self, widget, event, user_data=None):
        self.props.state = SearchView.State.ALL_ARTISTS
        self._headerbar.props.state = HeaderBar.State.SEARCH
        self._headerbar.props.title = _("Artists Results")
        self._headerbar.props.subtitle = None

        self._artist_all_flowbox.props.visible = True
        self._album_all_flowbox.props.visible = False
        self._artist_all_flowbox.bind_model(self._artist_model,
                                            self._create_artist_widget)

        self.props.visible_child = self._all_search_results
        self.props.search_mode_active = False

    @Gtk.Template.Callback()
    def _on_all_albums_clicked(self, widget, event, user_data=None):
        self.props.state = SearchView.State.ALL_ALBUMS
        self._headerbar.props.state = HeaderBar.State.SEARCH
        self._headerbar.props.title = _("Albums Results")
        self._headerbar.props.subtitle = None

        self._artist_all_flowbox.props.visible = False
        self._album_all_flowbox.props.visible = True
        self._album_all_flowbox.bind_model(self._album_model,
                                           self._create_album_widget)

        self.props.visible_child = self._all_search_results
        self.props.search_mode_active = False

    def _select_all(self, value):
        def child_select(child):
            child.props.selected = value

        if self.props.state == SearchView.State.MAIN:
            with self._model.freeze_notify():

                def song_select(child):
                    song_widget = child.get_child()
                    song_widget.props.coresong.props.selected = value

                self._songs_listbox.foreach(song_select)
                self._album_flowbox.foreach(child_select)
                self._artist_flowbox.foreach(child_select)
        elif self.props.state == SearchView.State.ALL_ALBUMS:
            with self._model.freeze_notify():
                self._album_all_flowbox.foreach(child_select)
        elif self.props.state == SearchView.State.ALL_ARTISTS:
            with self._model.freeze_notify():
                self._artist_all_flowbox.foreach(child_select)
        elif self.props.state == SearchView.State.ALBUM:
            view = self.get_visible_child()
            if value is True:
                view.select_all()
            else:
                view.deselect_all()
        elif self.props.state == SearchView.State.ARTIST:
            view = self.get_visible_child().get_child().get_child()
            if value is True:
                view.select_all()
            else:
                view.deselect_all()

    def select_all(self):
        self._select_all(True)

    def deselect_all(self):
        self._select_all(False)

    def _back_button_clicked(self, widget, data=None):
        if self.get_visible_child() == self._search_results:
            return
        elif self.get_visible_child() == self._scrolled_artist_window:
            self.remove(self._scrolled_artist_window)
            self._scrolled_artist_window.destroy()
            self._scrolled_artist_window = None

        self.set_visible_child(self._search_results)
        self.props.search_mode_active = True
        self.props.state = SearchView.State.MAIN
        self._headerbar.props.state = HeaderBar.State.MAIN

    def _on_selection_mode_changed(self, widget, data=None):
        if (not self.props.selection_mode
                and self.get_parent().get_visible_child() == self):
            self.deselect_all()

    @GObject.Property(type=bool, default=False)
    def search_mode_active(self):
        """Get search mode status.

        :returns: the search mode status
        :rtype: bool
        """
        return self._search_mode_active

    @search_mode_active.setter
    def search_mode_active(self, value):
        """Set search mode status.

        :param bool mode: new search mode
        """
        # FIXME: search_mode_active should not change search_state.
        # This is necessary because Search state cannot interact with
        # the child views.
        self._search_mode_active = value
        if (not self._search_mode_active
                and self.get_visible_child() == self._search_results):
            self.props.search_state = Search.State.NONE
Beispiel #22
0
class SearchView(BaseView):

    search_state = GObject.Property(type=int, default=Search.State.NONE)

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

    @log
    def __init__(self, window, player):
        super().__init__('search', None, window)

        # FIXME: Searchbar handling does not belong here.
        self._searchbar = window._searchbar

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None

    @log
    def _setup_view(self):
        view_container = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
        self._box.pack_start(view_container, True, True, 0)

        self._view = Gtk.TreeView(
            activate_on_single_click=True, can_focus=False,
            halign=Gtk.Align.CENTER, headers_visible=False,
            show_expanders=False, width_request=530)
        self._view.get_style_context().add_class('view')
        self._view.get_style_context().add_class('content-view')
        self._view.get_selection().props.mode = Gtk.SelectionMode.NONE
        self._view.connect('row-activated', self._on_item_activated)

        self._ctrl = Gtk.GestureMultiPress().new(self._view)
        self._ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
        self._ctrl.connect("released", self._on_view_clicked)

        view_container.add(self._view)

    @log
    def _back_button_clicked(self, widget, data=None):
        self._searchbar.reveal(True, False)

        if self.get_visible_child() == self._artist_albums_widget:
            self._artist_albums_widget.destroy()
            self._artist_albums_widget = None
        elif self.get_visible_child() == self._grid:
            self._window.views[View.ALBUM].set_visible_child(
                self._window.views[View.ALBUM]._grid)

        self.set_visible_child(self._grid)
        self._headerbar.props.state = HeaderBar.State.MAIN

    @log
    def _on_item_activated(self, treeview, path, column):
        if self._star_handler.star_renderer_click:
            self._star_handler.star_renderer_click = False
            return

        if self.props.selection_mode:
            return

        try:
            child_path = self._filter_model.convert_path_to_child_path(path)
        except TypeError:
            return

        _iter = self.model.get_iter(child_path)
        if self.model[_iter][12] == 'album':
            title = self.model[_iter][2]
            artist = self.model[_iter][3]
            item = self.model[_iter][5]

            self._album_widget.update(item)
            self._headerbar.props.state = HeaderBar.State.SEARCH

            self._headerbar.props.title = title
            self._headerbar.props.subtitle = artist
            self.set_visible_child(self._album_widget)
            self.props.search_state = Search.State.NONE

        elif self.model[_iter][12] == 'artist':
            artist = self.model[_iter][2]
            albums = self._artists[artist.casefold()]['albums']

            self._artist_albums_widget = ArtistAlbumsWidget(
                artist, albums, self.player, self._window, True)
            self.add(self._artist_albums_widget)
            self._artist_albums_widget.show()

            self._artist_albums_widget.bind_property(
                'selected-items-count', self, 'selected-items-count')
            self.bind_property(
                'selection-mode', self._artist_albums_widget, 'selection-mode',
                GObject.BindingFlags.BIDIRECTIONAL)

            self._headerbar.props.state = HeaderBar.State.SEARCH
            self._headerbar.props.title = artist
            self._headerbar.props.subtitle = None
            self.set_visible_child(self._artist_albums_widget)
            self.props.search_state = Search.State.NONE
        elif self.model[_iter][12] == 'song':
            if self.model[_iter][11] != ValidationStatus.FAILED:
                c_iter = self._songs_model.convert_child_iter_to_iter(_iter)[1]
                self.player.set_playlist(
                    PlayerPlaylist.Type.SEARCH_RESULT, None, self._songs_model,
                    c_iter)
                self.player.play()
        else:  # Headers
            if self._view.row_expanded(path):
                self._view.collapse_row(path)
            else:
                self._view.expand_row(path, False)

    @log
    def _on_view_clicked(self, gesture, n_press, x, y):
        """Ctrl+click on self._view triggers selection mode."""
        _, state = Gtk.get_current_event_state()
        modifiers = Gtk.accelerator_get_default_mod_mask()
        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
                and not self.props.selection_mode):
            self._on_selection_mode_request()

        if (self.selection_mode
                and not self._star_handler.star_renderer_click):
            path, col, cell_x, cell_y = self._view.get_path_at_pos(x, y)
            iter_ = self.model.get_iter(path)
            self.model[iter_][6] = not self.model[iter_][6]
            selected_iters = self._get_selected_iters()

            self.props.selected_items_count = len(selected_iters)

    @log
    def _get_selected_iters(self):
        iters = []
        for row in self.model:
            iter_child = self.model.iter_children(row.iter)
            while iter_child is not None:
                if self.model[iter_child][6]:
                    iters.append(iter_child)
                iter_child = self.model.iter_next(iter_child)
        return iters

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        super()._on_selection_mode_changed(widget, data)

        col = self._view.get_columns()[0]
        cells = col.get_cells()
        cells[4].props.visible = self.props.selection_mode
        col.queue_resize()

    @log
    def _add_search_item(self, source, param, item, remaining=0, data=None):
        if not item:
            if (grilo._search_callback_counter == 0
                    and grilo.search_source):
                self.props.search_state = Search.State.NO_RESULT
            return

        if data != self.model:
            return

        artist = utils.get_artist_name(item)
        album = utils.get_album_title(item)

        key = '%s-%s' % (artist, album)
        if key not in self._albums:
            self._albums[key] = Grl.Media()
            self._albums[key].set_title(album)
            self._albums[key].add_artist(artist)
            self._albums[key].set_source(source.get_id())
            self._albums[key].songs = []
            self._add_item(
                source, None, self._albums[key], 0, [self.model, 'album'])
            self._add_item(
                source, None, self._albums[key], 0, [self.model, 'artist'])

        self._albums[key].songs.append(item)
        self._add_item(source, None, item, 0, [self.model, 'song'])

    @log
    def _retrieval_finished(self, klass, model, _iter):
        if not model[_iter][13]:
            return

        model[_iter][13] = klass.surface

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if data is None:
            return

        model, category = data

        self._items_found = (
            self.model.iter_n_children(self._head_iters[0])
            + self.model.iter_n_children(self._head_iters[1])
            + self.model.iter_n_children(self._head_iters[2])
            + self.model.iter_n_children(self._head_iters[3])
        )

        # We need to remember the view before the search view
        emptysearchview = self._window.views[View.EMPTY]
        if (self._window.curr_view != emptysearchview
                and self._window.prev_view != emptysearchview):
            self.previous_view = self._window.prev_view

        if self._items_found == 0:
            self.props.search_state = Search.State.NO_RESULT
        else:
            self.props.search_state = Search.State.RESULT

        if remaining == 0:
            self._window.notifications_popup.pop_loading()
            self._view.show()

        if not item or model != self.model:
            return

        self._offset += 1
        title = utils.get_media_title(item)
        item.set_title(title)
        artist = utils.get_artist_name(item)

        group = 3
        try:
            group = {'album': 0, 'artist': 1, 'song': 2}[category]
        except KeyError:
            pass

        _iter = None
        if category == 'album':
            _iter = self.model.insert_with_values(
                self._head_iters[group], -1, [0, 2, 3, 5, 9, 12],
                [str(item.get_id()), title, artist, item, 2,
                 category])
        elif category == 'song':
            # FIXME: source specific hack
            if source.get_id() != 'grl-tracker-source':
                fav = 2
            else:
                fav = item.get_favourite()
            _iter = self.model.insert_with_values(
                self._head_iters[group], -1, [0, 2, 3, 5, 9, 12],
                [str(item.get_id()), title, artist, item, fav,
                 category])
        else:
            if not artist.casefold() in self._artists:
                _iter = self.model.insert_with_values(
                    self._head_iters[group], -1, [0, 2, 5, 9, 12],
                    [str(item.get_id()), artist, item, 2,
                     category])
                self._artists[artist.casefold()] = {
                    'iter': _iter,
                    'albums': []
                }
            self._artists[artist.casefold()]['albums'].append(item)

        # FIXME: Figure out why iter can be None here, seems illogical.
        if _iter is not None:
            scale = self._view.get_scale_factor()
            art = Art(Art.Size.SMALL, item, scale)
            self.model[_iter][13] = art.surface
            art.connect(
                'finished', self._retrieval_finished, self.model, _iter)
            art.lookup()

        if self.model.iter_n_children(self._head_iters[group]) == 1:
            path = self.model.get_path(self._head_iters[group])
            path = self._filter_model.convert_child_path_to_path(path)
            self._view.expand_row(path, False)

    @log
    def _add_list_renderers(self):
        column = Gtk.TreeViewColumn()

        # Add our own surface renderer, instead of the one provided by
        # Gd. This avoids us having to set the model to a cairo.Surface
        # which is currently not a working solution in pygobject.
        # https://gitlab.gnome.org/GNOME/pygobject/issues/155
        pixbuf_renderer = Gtk.CellRendererPixbuf(
            xalign=0.5, yalign=0.5, xpad=12, ypad=2)
        column.pack_start(pixbuf_renderer, False)
        column.set_cell_data_func(
            pixbuf_renderer, self._on_list_widget_pixbuf_renderer)
        column.add_attribute(pixbuf_renderer, 'surface', 13)

        # With the bugfix in libgd 9117650bda, the search results
        # stopped aligning at the top. With the artists results not
        # having a second line of text, this looks off.
        # Revert to old behaviour by forcing the alignment to be top.
        two_lines_renderer = Gd.TwoLinesRenderer(
            wrap_mode=Pango.WrapMode.WORD_CHAR, xpad=12, xalign=0.0,
            yalign=0, text_lines=2)
        column.pack_start(two_lines_renderer, True)
        column.set_cell_data_func(
            two_lines_renderer, self._on_list_widget_two_lines_renderer)
        column.add_attribute(two_lines_renderer, 'text', 2)
        column.add_attribute(two_lines_renderer, 'line_two', 3)

        title_renderer = Gtk.CellRendererText(
            xpad=12, xalign=0.0, yalign=0.5, height=32,
            ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD)
        column.pack_start(title_renderer, False)
        column.set_cell_data_func(
            title_renderer, self._on_list_widget_title_renderer)
        column.add_attribute(title_renderer, 'text', 2)

        self._star_handler.add_star_renderers(column)

        selection_renderer = Gtk.CellRendererToggle(xpad=12, xalign=1.0)
        column.pack_start(selection_renderer, False)
        column.set_cell_data_func(
            selection_renderer, self._on_list_widget_selection_renderer)
        column.add_attribute(selection_renderer, 'active', 6)

        self._view.append_column(column)

    @log
    def _is_header(self, model, iter_):
        return model.iter_parent(iter_) is None

    @log
    def _on_list_widget_title_renderer(self, col, cell, model, iter_, data):
        cell.props.visible = self._is_header(model, iter_)

    @log
    def _on_list_widget_pixbuf_renderer(self, col, cell, model, iter_, data):
        if (not model[iter_][13]
                or self._is_header(model, iter_)):
            cell.props.visible = False
            return

        cell.props.surface = model[iter_][13]
        cell.props.visible = True

    @log
    def _on_list_widget_two_lines_renderer(
            self, col, cell, model, iter_, data):
        if self._is_header(model, iter_):
            cell.props.visible = False
            return

        cell.props.visible = True

    @log
    def _on_list_widget_selection_renderer(
            self, col, cell, model, iter_, data):
        if (self.props.selection_mode
                and not self._is_header(model, iter_)):
            cell.props.visible = True
        else:
            cell.props.visible = False

    @log
    def populate(self):
        self._init = True
        self._headerbar.props.state = HeaderBar.State.MAIN

    @log
    def get_selected_songs(self, callback):
        if self.get_visible_child() == self._album_widget:
            callback(self._album_widget.get_selected_songs())
        elif self.get_visible_child() == self._artist_albums_widget:
            callback(self._artist_albums_widget.get_selected_songs())
        else:
            self._albums_index = 0
            self._artists_albums_index = 0
            self._items_selected = []
            self._items_selected_callback = callback
            self._get_selected_albums()

    @log
    def _get_selected_albums(self):
        selected_iters = self._get_selected_iters()

        self._albums_selected = [
            self.model[iter_][5]
            for iter_ in selected_iters
            if self.model[iter_][12] == 'album']

        if len(self._albums_selected):
            self._get_selected_albums_songs()
        else:
            self._get_selected_artists()

    @log
    def _get_selected_albums_songs(self):
        grilo.populate_album_songs(
            self._albums_selected[self._albums_index],
            self._add_selected_albums_songs)
        self._albums_index += 1

    @log
    def _add_selected_albums_songs(
            self, source, param, item, remaining=0, data=None):
        if item:
            self._items_selected.append(item)
        if remaining == 0:
            if self._albums_index < len(self._albums_selected):
                self._get_selected_albums_songs()
            else:
                self._get_selected_artists()

    @log
    def _get_selected_artists(self):
        selected_iters = self._get_selected_iters()

        artists_selected = [
            self._artists[self.model[iter_][2].casefold()]
            for iter_ in selected_iters
            if self.model[iter_][12] == 'artist']

        self._artists_albums_selected = []
        for artist in artists_selected:
            self._artists_albums_selected.extend(artist['albums'])

        if len(self._artists_albums_selected):
            self._get_selected_artists_albums_songs()
        else:
            self._get_selected_songs()

    @log
    def _get_selected_artists_albums_songs(self):
        grilo.populate_album_songs(
            self._artists_albums_selected[self._artists_albums_index],
            self._add_selected_artists_albums_songs)
        self._artists_albums_index += 1

    @log
    def _add_selected_artists_albums_songs(
            self, source, param, item, remaining=0, data=None):
        if item:
            self._items_selected.append(item)
        if remaining == 0:
            artist_albums = len(self._artists_albums_selected)
            if self._artists_albums_index < artist_albums:
                self._get_selected_artists_albums_songs()
            else:
                self._get_selected_songs()

    @log
    def _get_selected_songs(self):
        selected_iters = self._get_selected_iters()
        self._items_selected.extend([
            self.model[iter_][5]
            for iter_ in selected_iters
            if self.model[iter_][12] == 'song'])
        self._items_selected_callback(self._items_selected)

    @log
    def _filter_visible_func(self, model, _iter, data=None):
        top_level = model.iter_parent(_iter) is None
        visible = (not top_level or model.iter_has_child(_iter))

        return visible

    @log
    def set_search_text(self, search_term, fields_filter):
        query_matcher = {
            'album': {
                'search_all': Query.get_albums_with_any_match,
                'search_artist': Query.get_albums_with_artist_match,
                'search_composer': Query.get_albums_with_composer_match,
                'search_album': Query.get_albums_with_album_match,
                'search_track': Query.get_albums_with_track_match,
            },
            'artist': {
                'search_all': Query.get_artists_with_any_match,
                'search_artist': Query.get_artists_with_artist_match,
                'search_composer': Query.get_artists_with_composer_match,
                'search_album': Query.get_artists_with_album_match,
                'search_track': Query.get_artists_with_track_match,
            },
            'song': {
                'search_all': Query.get_songs_with_any_match,
                'search_artist': Query.get_songs_with_artist_match,
                'search_composer': Query.get_songs_with_composer_match,
                'search_album': Query.get_songs_with_album_match,
                'search_track': Query.get_songs_with_track_match,
            },
        }

        self.model = Gtk.TreeStore(
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,    # item title or header text
            GObject.TYPE_STRING,    # artist for albums and songs
            GdkPixbuf.Pixbuf,       # Gd placeholder album art
            GObject.TYPE_OBJECT,    # item
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,
            GObject.TYPE_STRING,
            GObject.TYPE_INT,
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,       # validation status
            GObject.TYPE_STRING,    # type
            object                  # album art surface
        )

        self._filter_model = self.model.filter_new(None)
        self._filter_model.set_visible_func(self._filter_visible_func)
        self._view.set_model(self._filter_model)

        self._albums = {}
        self._artists = {}

        if search_term == "":
            return

        albums_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Albums"), 2])
        artists_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Artists"), 2])
        songs_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Songs"), 2])
        playlists_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Playlists"), 2])

        self._head_iters = [
            albums_iter,
            artists_iter,
            songs_iter,
            playlists_iter
        ]

        self._songs_model = self.model.filter_new(
            self.model.get_path(songs_iter))

        # Use queries for Tracker
        if (not grilo.search_source
                or grilo.search_source.get_id() == 'grl-tracker-source'):
            for category in ('album', 'artist', 'song'):
                query = query_matcher[category][fields_filter](search_term)
                self._window.notifications_popup.push_loading()
                grilo.populate_custom_query(
                    query, self._add_item, -1, [self.model, category])
        if (not grilo.search_source
                or grilo.search_source.get_id() != 'grl-tracker-source'):
            # nope, can't do - reverting to Search
            grilo.search(search_term, self._add_search_item, self.model)
Beispiel #23
0
    def __init__(self, application):
        """Initialize SearchView

        :param GtkApplication application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        self.props.name = "search"

        self._application = application
        self._coremodel = application.props.coremodel
        self._model = self._coremodel.props.songs_search
        self._album_model = self._coremodel.props.albums_search
        self._album_filter = self._coremodel.props.albums_search_filter
        self._album_filter.set_filter_func(self._core_filter,
                                           self._album_model, 12)

        self._artist_model = self._coremodel.props.artists_search
        self._artist_filter = self._coremodel.props.artists_search_filter
        self._artist_filter.set_filter_func(self._core_filter,
                                            self._artist_model, 6)

        self._model.connect_after("items-changed",
                                  self._on_model_items_changed)
        self._songs_listbox.bind_model(self._model, self._create_song_widget)
        self._on_model_items_changed(self._model, 0, 0, 0)

        self._album_filter.connect_after("items-changed",
                                         self._on_album_model_items_changed)
        self._album_flowbox.bind_model(self._album_filter,
                                       self._create_album_widget)
        self._album_flowbox.connect("size-allocate",
                                    self._on_album_flowbox_size_allocate)
        self._on_album_model_items_changed(self._album_filter, 0, 0, 0)

        self._artist_filter.connect_after("items-changed",
                                          self._on_artist_model_items_changed)
        self._artist_flowbox.bind_model(self._artist_filter,
                                        self._create_artist_widget)
        self._artist_flowbox.connect("size-allocate",
                                     self._on_artist_flowbox_size_allocate)
        self._on_artist_model_items_changed(self._artist_filter, 0, 0, 0)

        self._player = self._application.props.player

        self._window = application.props.window
        self._headerbar = self._window._headerbar

        self.connect("notify::selection-mode", self._on_selection_mode_changed)

        self.bind_property('selection-mode', self._window, 'selection-mode',
                           GObject.BindingFlags.BIDIRECTIONAL)

        self._album_widget = AlbumWidget(self._application)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self._scrolled_artist_window = None

        self._search_mode_active = False
Beispiel #24
0
class SearchView(BaseView):
    __gsignals__ = {
        'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ())
    }

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

    @log
    def __init__(self, window, player):
        BaseView.__init__(self, 'search', None, window, Gd.MainViewType.LIST)
        self._items = {}
        self.isStarred = None
        self.iter_to_clean = None

        scale = self.get_scale_factor()
        loading_icon_surface = DefaultIcon(scale).get(DefaultIcon.Type.loading,
                                                      ArtSize.small)
        self._loading_icon = Gdk.pixbuf_get_from_surface(
            loading_icon_surface, 0, 0, loading_icon_surface.get_width(),
            loading_icon_surface.get_height())

        self._add_list_renderers()
        self.player = player
        self.head_iters = [None, None, None, None]
        self.songs_model = self.model
        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self.albums_selected = []
        self._albums = {}
        self._albumWidget = AlbumWidget(player, self)
        self.add(self._albumWidget)

        self.artists_albums_selected = []
        self._artists = {}
        self._artistAlbumsWidget = None

        self._view.get_generic_view().set_show_expanders(False)
        self.items_selected = []
        self.items_selected_callback = None

        self.found_items_number = None

    @log
    def _no_music_found_callback(self, view):
        self._window._stack.set_visible_child_name('emptysearch')
        emptysearch = self._window._stack.get_child_by_name('emptysearch')
        emptysearch._artistAlbumsWidget = self._artistAlbumsWidget

    @log
    def _back_button_clicked(self, widget, data=None):
        self._header_bar.searchbar.show_bar(True, False)
        if self.get_visible_child() == self._artistAlbumsWidget:
            self._artistAlbumsWidget.destroy()
            self._artistAlbumsWidget = None
        elif self.get_visible_child() == self._grid:
            self._window.views[0].set_visible_child(
                self._window.views[0]._grid)
        self.set_visible_child(self._grid)
        self._window.toolbar.set_state(ToolbarState.MAIN)

    @log
    def _on_item_activated(self, widget, id, path):
        if self._star_handler.star_renderer_click:
            self._star_handler.star_renderer_click = False
            return

        try:
            child_path = self.filter_model.convert_path_to_child_path(path)
        except TypeError:
            return
        _iter = self.model.get_iter(child_path)
        if self.model[_iter][11] == 'album':
            title = self.model.get_value(_iter, 2)
            artist = self.model.get_value(_iter, 3)
            item = self.model.get_value(_iter, 5)
            self._albumWidget.update(artist, title, item, self._header_bar,
                                     self._selection_toolbar)
            self._header_bar.set_state(ToolbarState.SEARCH_VIEW)
            title = utils.get_media_title(item)
            self._header_bar.header_bar.set_title(title)
            self._header_bar.header_bar.sub_title = artist
            self.set_visible_child(self._albumWidget)
            self._header_bar.searchbar.show_bar(False)
        elif self.model[_iter][11] == 'artist':
            artist = self.model.get_value(_iter, 2)
            albums = self._artists[artist.casefold()]['albums']

            self._artistAlbumsWidget = ArtistAlbumsWidget(
                artist, albums, self.player, self._header_bar,
                self._selection_toolbar, self._window, True)
            self.add(self._artistAlbumsWidget)
            self._artistAlbumsWidget.show()

            self._header_bar.set_state(ToolbarState.SEARCH_VIEW)
            self._header_bar.header_bar.set_title(artist)
            self.set_visible_child(self._artistAlbumsWidget)
            self._header_bar.searchbar.show_bar(False)
        elif self.model[_iter][11] == 'song':
            if self.model.get_value(_iter, 12) != DiscoveryStatus.FAILED:
                child_iter = self.songs_model.convert_child_iter_to_iter(
                    _iter)[1]
                self.player.set_playlist('Search Results', None,
                                         self.songs_model, child_iter, 5, 12)
                self.player.set_playing(True)
        else:  # Headers
            if self._view.get_generic_view().row_expanded(path):
                self._view.get_generic_view().collapse_row(path)
            else:
                self._view.get_generic_view().expand_row(path, False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if self._artistAlbumsWidget is not None and self.get_visible_child(
        ) == self._artistAlbumsWidget:
            self._artistAlbumsWidget.set_selection_mode(
                self._header_bar._selectionMode)

    @log
    def _add_search_item(self, source, param, item, remaining=0, data=None):
        if not item:
            if grilo._search_callback_counter == 0 and grilo.search_source:
                self.emit('no-music-found')
            return

        if data != self.model:
            return

        artist = utils.get_artist_name(item)
        album = utils.get_album_title(item)
        composer = item.get_composer()

        key = '%s-%s' % (artist, album)
        if key not in self._albums:
            self._albums[key] = Grl.Media()
            self._albums[key].set_title(album)
            self._albums[key].add_artist(artist)
            self._albums[key].set_composer(composer)
            self._albums[key].set_source(source.get_id())
            self._albums[key].songs = []
            self._add_item(source, None, self._albums[key], 0,
                           [self.model, 'album'])
            self._add_item(source, None, self._albums[key], 0,
                           [self.model, 'artist'])

        self._albums[key].songs.append(item)
        self._add_item(source, None, item, 0, [self.model, 'song'])

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if data is None:
            return

        model, category = data

        self.found_items_number = (
            self.model.iter_n_children(self.head_iters[0]) +
            self.model.iter_n_children(self.head_iters[1]) +
            self.model.iter_n_children(self.head_iters[2]) +
            self.model.iter_n_children(self.head_iters[3]))

        if category == 'song' and self.found_items_number == 0 and remaining == 0:
            if grilo.search_source:
                self.emit('no-music-found')

        # We need to remember the view before the search view
        if self._window.curr_view != self._window.views[5] and \
           self._window.prev_view != self._window.views[5]:
            self.previous_view = self._window.prev_view

        if remaining == 0:
            self._window.pop_loading_notification()
            self._view.show()

        if not item or model != self.model:
            return

        self._offset += 1
        title = utils.get_media_title(item)
        item.set_title(title)
        artist = utils.get_artist_name(item)
        # FIXME: Can't be None in treemodel
        composer = item.get_composer() or ""

        group = 3
        try:
            group = {'album': 0, 'artist': 1, 'song': 2}[category]
        except:
            pass

        # FIXME: HiDPI icon lookups return a surface that can't be
        # scaled by GdkPixbuf, so it results in a * scale factor sized
        # icon for the search view.
        _iter = None
        if category == 'album':
            _iter = self.model.insert_with_values(
                self.head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11, 13], [
                    str(item.get_id()), title, artist, self._loading_icon,
                    item, 2, category, composer
                ])
            self._cache.lookup(item, ArtSize.small, self._on_lookup_ready,
                               _iter)
        elif category == 'song':
            _iter = self.model.insert_with_values(
                self.head_iters[group], -1,
                [0, 2, 3, 4, 5, 9, 11, 13],
                [str(item.get_id()), title, artist,
                 self._loading_icon, item,
                 2 if source.get_id() != 'grl-tracker-source' \
                    else item.get_favourite(), category, composer])
            self._cache.lookup(item, ArtSize.small, self._on_lookup_ready,
                               _iter)
        else:
            if not artist.casefold() in self._artists:
                _iter = self.model.insert_with_values(
                    self.head_iters[group], -1, [0, 2, 4, 5, 9, 11, 13], [
                        str(item.get_id()), artist, self._loading_icon, item,
                        2, category, composer
                    ])
                self._cache.lookup(item, ArtSize.small, self._on_lookup_ready,
                                   _iter)
                self._artists[artist.casefold()] = {
                    'iter': _iter,
                    'albums': []
                }

            self._artists[artist.casefold()]['albums'].append(item)

        if self.model.iter_n_children(self.head_iters[group]) == 1:
            path = self.model.get_path(self.head_iters[group])
            path = self.filter_model.convert_child_path_to_path(path)
            self._view.get_generic_view().expand_row(path, False)

    @log
    def _add_list_renderers(self):
        list_widget = self._view.get_generic_view()
        list_widget.set_halign(Gtk.Align.CENTER)
        list_widget.set_size_request(530, -1)
        cols = list_widget.get_columns()

        title_renderer = Gtk.CellRendererText(
            xpad=12,
            xalign=0.0,
            yalign=0.5,
            height=32,
            ellipsize=Pango.EllipsizeMode.END,
            weight=Pango.Weight.BOLD)
        list_widget.add_renderer(title_renderer,
                                 self._on_list_widget_title_render, None)
        cols[0].add_attribute(title_renderer, 'text', 2)

        self._star_handler.add_star_renderers(list_widget, cols[0])

        cells = cols[0].get_cells()
        cols[0].reorder(cells[0], -1)
        cols[0].set_cell_data_func(cells[0],
                                   self._on_list_widget_selection_render, None)

    def _on_list_widget_selection_render(self, col, cell, model, _iter, data):
        cell.set_visible(self._view.get_selection_mode()
                         and model.iter_parent(_iter) is not None)

    def _on_list_widget_title_render(self, col, cell, model, _iter, data):
        cells = col.get_cells()
        cells[0].set_visible(model.iter_parent(_iter) is not None)
        cells[1].set_visible(model.iter_parent(_iter) is not None)
        cells[2].set_visible(model.iter_parent(_iter) is None)

    @log
    def populate(self):
        self._init = True
        self._window.push_loading_notification()
        self._header_bar.set_state(ToolbarState.MAIN)

    @log
    def get_selected_songs(self, callback):
        if self.get_visible_child() == self._albumWidget:
            callback(self._albumWidget.view.get_selected_items())
        elif self.get_visible_child() == self._artistAlbumsWidget:
            items = []
            # FIXME: calling into private model
            for row in self._artistAlbumsWidget._model:
                if row[6]:
                    items.append(row[5])
            callback(items)
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self._get_selected_albums()

    @log
    def _get_selected_albums(self):
        self.albums_index = 0
        self.albums_selected = [
            self.model[child_path][5] for child_path in [
                self.filter_model.convert_path_to_child_path(path)
                for path in self._view.get_selection()
            ] if self.model[child_path][11] == 'album'
        ]
        if len(self.albums_selected):
            self._get_selected_albums_songs()
        else:
            self._get_selected_artists()

    @log
    def _get_selected_albums_songs(self):
        grilo.populate_album_songs(self.albums_selected[self.albums_index],
                                   self._add_selected_albums_songs)
        self.albums_index += 1

    @log
    def _add_selected_albums_songs(self,
                                   source,
                                   param,
                                   item,
                                   remaining=0,
                                   data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < len(self.albums_selected):
                self._get_selected_albums_songs()
            else:
                self._get_selected_artists()

    @log
    def _get_selected_artists(self):
        self.artists_albums_index = 0
        self.artists_selected = [
            self._artists[self.model[child_path][2].casefold()]
            for child_path in [
                self.filter_model.convert_path_to_child_path(path)
                for path in self._view.get_selection()
            ] if self.model[child_path][11] == 'artist'
        ]

        self.artists_albums_selected = []
        for artist in self.artists_selected:
            self.artists_albums_selected.extend(artist['albums'])

        if len(self.artists_albums_selected):
            self._get_selected_artists_albums_songs()
        else:
            self._get_selected_songs()

    @log
    def _get_selected_artists_albums_songs(self):
        grilo.populate_album_songs(
            self.artists_albums_selected[self.artists_albums_index],
            self._add_selected_artists_albums_songs)
        self.artists_albums_index += 1

    @log
    def _add_selected_artists_albums_songs(self,
                                           source,
                                           param,
                                           item,
                                           remaining=0,
                                           data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.artists_albums_index < len(self.artists_albums_selected):
                self._get_selected_artists_albums_songs()
            else:
                self._get_selected_songs()

    @log
    def _get_selected_songs(self):
        self.items_selected.extend([
            self.model[child_path][5] for child_path in [
                self.filter_model.convert_path_to_child_path(path)
                for path in self._view.get_selection()
            ] if self.model[child_path][11] == 'song'
        ])
        self.items_selected_callback(self.items_selected)

    @log
    def _filter_visible_func(self, model, _iter, data=None):
        return model.iter_parent(_iter) is not None or model.iter_has_child(
            _iter)

    @log
    def set_search_text(self, search_term, fields_filter):
        query_matcher = {
            'album': {
                'search_all': Query.get_albums_with_any_match,
                'search_artist': Query.get_albums_with_artist_match,
                'search_composer': Query.get_albums_with_composer_match,
                'search_album': Query.get_albums_with_album_match,
                'search_track': Query.get_albums_with_track_match,
            },
            'artist': {
                'search_all': Query.get_artists_with_any_match,
                'search_artist': Query.get_artists_with_artist_match,
                'search_composer': Query.get_artists_with_composer_match,
                'search_album': Query.get_artists_with_album_match,
                'search_track': Query.get_artists_with_track_match,
            },
            'song': {
                'search_all': Query.get_songs_with_any_match,
                'search_artist': Query.get_songs_with_artist_match,
                'search_composer': Query.get_songs_with_composer_match,
                'search_album': Query.get_songs_with_album_match,
                'search_track': Query.get_songs_with_track_match,
            },
        }

        self.model = Gtk.TreeStore(
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,  # item title or header text
            GObject.TYPE_STRING,  # artist for albums and songs
            GdkPixbuf.Pixbuf,  # album art
            GObject.TYPE_OBJECT,  # item
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,
            GObject.TYPE_STRING,
            GObject.TYPE_INT,
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_STRING,  # type
            GObject.TYPE_INT,
            GObject.TYPE_STRING,  # composer
        )
        self.filter_model = self.model.filter_new(None)
        self.filter_model.set_visible_func(self._filter_visible_func)
        self._view.set_model(self.filter_model)

        self._albums = {}
        self._artists = {}

        if search_term == "":
            return

        albums_iter = self.model.insert_with_values(None, -1, [2, 9],
                                                    [_("Albums"), 2])
        artists_iter = self.model.insert_with_values(None, -1, [2, 9],
                                                     [_("Artists"), 2])
        songs_iter = self.model.insert_with_values(None, -1, [2, 9],
                                                   [_("Songs"), 2])
        playlists_iter = self.model.insert_with_values(None, -1, [2, 9],
                                                       [_("Playlists"), 2])

        self.head_iters = [
            albums_iter, artists_iter, songs_iter, playlists_iter
        ]
        self.songs_model = self.model.filter_new(
            self.model.get_path(songs_iter))

        # Use queries for Tracker
        if not grilo.search_source or \
           grilo.search_source.get_id() == 'grl-tracker-source':
            for category in ('album', 'artist', 'song'):
                query = query_matcher[category][fields_filter](search_term)
                grilo.populate_custom_query(query, self._add_item, -1,
                                            [self.model, category])
        if not grilo.search_source or \
           grilo.search_source.get_id() != 'grl-tracker-source':
            # nope, can't do - reverting to Search
            grilo.search(search_term, self._add_search_item, self.model)
Beispiel #25
0
class AlbumsView(BaseView):

    search_mode_active = GObject.Property(type=bool, default=False)

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

    @log
    def __init__(self, window, player):
        self._window = window
        super().__init__('albums', _("Albums"), window)

        self.player = player
        self._album_widget = AlbumWidget(player, self)
        self._album_widget.bind_property(
            "selection-mode", self, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None

        self.connect(
            "notify::search-mode-active", self._on_search_mode_changed)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        super()._on_selection_mode_changed(widget, data)

        if not self.props.selection_mode:
            self._on_changes_pending()

    @log
    def _on_search_mode_changed(self, klass, param):
        if (not self.props.search_mode_active
                and self._headerbar.props.stack.props.visible_child == self
                and self.get_visible_child() == self._album_widget):
            self._set_album_headerbar(self._album_widget.props.album)

    @log
    def _setup_view(self):
        self._view = Gtk.FlowBox(
            homogeneous=True, hexpand=True, halign=Gtk.Align.FILL,
            valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE,
            margin=18, row_spacing=12, column_spacing=6,
            min_children_per_line=1, max_children_per_line=20, visible=True)

        self._view.get_style_context().add_class('content-view')
        self._view.connect('child-activated', self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self._view)
        scrolledwin.show()

        self._box.add(scrolledwin)

        self._model = self._window._app.props.coremodel.props.albums_sort
        self._view.bind_model(self._model, self._create_widget)

        self._view.show()

    @log
    def _create_widget(self, corealbum):
        album_widget = AlbumCover(corealbum)

        self.bind_property(
            "selection-mode", album_widget, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        # NOTE: Adding SYNC_CREATE here will trigger all the nested
        # models to be created. This will slow down initial start,
        # but will improve initial 'selecte all' speed.
        album_widget.bind_property(
            "selected", corealbum, "selected",
            GObject.BindingFlags.BIDIRECTIONAL)

        return album_widget

    @log
    def _back_button_clicked(self, widget, data=None):
        self._headerbar.state = HeaderBar.State.MAIN
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        corealbum = child.props.corealbum
        if self.props.selection_mode:
            return

        # Update and display the album widget if not in selection mode
        self._album_widget.update(corealbum)

        self._set_album_headerbar(corealbum)
        self.set_visible_child(self._album_widget)

    @log
    def _set_album_headerbar(self, corealbum):
        self._headerbar.props.state = HeaderBar.State.CHILD
        self._headerbar.props.title = corealbum.props.title
        self._headerbar.props.subtitle = corealbum.props.artist

    @log
    def _populate(self, data=None):
        # self._window.notifications_popup.push_loading()
        self._init = True
        self._view.show()

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        with self._window._app.props.coreselection.freeze_notify():
            for child in self._view.get_children():
                child.props.selected = selected
                child.props.corealbum.props.selected = selected

    def select_all(self):
        self._toggle_all_selection(True)

    def unselect_all(self):
        self._toggle_all_selection(False)
Beispiel #26
0
class AlbumsView(BaseView):
    def __repr__(self):
        return '<AlbumsView>'

    @log
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window, None)

        self._queue = LifoQueue()
        self._album_widget = AlbumWidget(player, self)
        self.player = player
        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None
        self._add_list_renderers()
        self._init = True

    @log
    def _on_changes_pending(self, data=None):
        if (self._init and not self._header_bar.selection_mode):
            self._offset = 0
            self._init = True
            GLib.idle_add(self.populate)
            grilo.changes_pending['Albums'] = False

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if (not self._header_bar.selection_mode
                and grilo.changes_pending['Albums']):
            self._on_changes_pending()

    @log
    def _setup_view(self, view_type):
        self._view = Gtk.FlowBox(homogeneous=True,
                                 hexpand=True,
                                 halign=Gtk.Align.FILL,
                                 valign=Gtk.Align.START,
                                 selection_mode=Gtk.SelectionMode.NONE,
                                 margin=18,
                                 row_spacing=12,
                                 column_spacing=6,
                                 min_children_per_line=1,
                                 max_children_per_line=25)

        self._view.get_style_context().add_class('content-view')
        self._view.connect('child-activated', self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self._view)
        scrolledwin.show()

        self._box.add(scrolledwin)

    @log
    def _back_button_clicked(self, widget, data=None):
        self._header_bar.reset_header_title()
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        item = child.media_item

        if self._star_handler.star_renderer_click:
            self._star_handler.star_renderer_click = False
            return

        # Toggle the selection when in selection mode
        if self.selection_mode:
            child.check.set_active(not child.check.get_active())
            return

        title = utils.get_media_title(item)
        self._escaped_title = title
        self._artist = utils.get_artist_name(item)

        self._album_widget.update(self._artist, title, item, self._header_bar,
                                  self._selection_toolbar)

        self._header_bar.set_state(ToolbarState.CHILD_VIEW)
        self._header_bar.header_bar.set_title(self._escaped_title)
        self._header_bar.header_bar.sub_title = self._artist
        self.set_visible_child(self._album_widget)

    @log
    def update_title(self):
        self._header_bar.header_bar.set_title(self._escaped_title)
        self._header_bar.header_bar.sub_title = self._artist

    @log
    def populate(self):
        self._window.notifications_popup.push_loading()
        grilo.populate_albums(self._offset, self._add_item)

    @log
    def get_selected_songs(self, callback):
        # FIXME: we call into private objects with full knowledge of
        # what is there
        if self._header_bar._state == ToolbarState.CHILD_VIEW:
            callback(self._album_widget._disc_listbox.get_selected_items())
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self.albums_index = 0
            if len(self.albums_selected):
                self._get_selected_album_songs()

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if item:
            # Store all items to optimize 'Select All' action
            self.all_items.append(item)

            # Add to the flowbox
            child = self._create_album_item(item)
            self._view.add(child)
        elif remaining == 0:
            self._view.show()

            # FIXME: To work around slow updating of the albumsview,
            # load album covers with a fixed delay. This results in a
            # quick first show with a placeholder cover and then a
            # reasonably responsive view while loading the actual
            # covers.
            while not self._queue.empty():
                func, arg = self._queue.get()
                GLib.timeout_add(65 * self._queue.qsize(),
                                 func,
                                 arg,
                                 priority=GLib.PRIORITY_LOW)

            self._window.notifications_popup.pop_loading()

    def _create_album_item(self, item):
        artist = utils.get_artist_name(item)
        title = utils.get_media_title(item)

        builder = Gtk.Builder.new_from_resource(
            '/org/gnome/Music/AlbumCover.ui')

        child = Gtk.FlowBoxChild()
        child.get_style_context().add_class('tile')
        child.stack = builder.get_object('stack')
        child.check = builder.get_object('check')
        child.title = builder.get_object('title')
        child.subtitle = builder.get_object('subtitle')
        child.events = builder.get_object('events')
        child.media_item = item

        child.title.set_label(title)
        child.subtitle.set_label(artist)

        child.events.add_events(Gdk.EventMask.TOUCH_MASK)

        child.events.connect('button-release-event',
                             self._on_album_event_triggered, child)

        child.check_handler_id = child.check.connect('notify::active',
                                                     self._on_child_toggled,
                                                     child)

        child.check.bind_property('visible', self, 'selection_mode',
                                  GObject.BindingFlags.BIDIRECTIONAL)

        child.add(builder.get_object('main_box'))
        child.show()

        cover_stack = CoverStack(child.stack, Art.Size.MEDIUM)
        self._queue.put((cover_stack.update, item))

        return child

    @log
    def _on_album_event_triggered(self, evbox, event, child):
        if event.button is 3:
            self._on_selection_mode_request()
            if self.selection_mode:
                child.check.set_active(True)

    @log
    def _on_child_toggled(self, check, pspec, child):
        if (check.get_active()
                and child.media_item not in self.albums_selected):
            self.albums_selected.append(child.media_item)
        elif (not check.get_active()
              and child.media_item in self.albums_selected):
            self.albums_selected.remove(child.media_item)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def _get_selected_album_songs(self):
        grilo.populate_album_songs(self.albums_selected[self.albums_index],
                                   self._add_selected_item)
        self.albums_index += 1

    @log
    def _add_selected_item(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < len(self.albums_selected):
                self._get_selected_album_songs()
            else:
                self.items_selected_callback(self.items_selected)

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        for child in self._view.get_children():
            GObject.signal_handler_block(child.check, child.check_handler_id)

            # Set the checkbutton state without emiting the signal
            child.check.set_active(selected)

            GObject.signal_handler_unblock(child.check, child.check_handler_id)

        self.update_header_from_selection(len(self.albums_selected))

    @log
    def select_all(self):
        self.albums_selected = list(self.all_items)
        self._toggle_all_selection(True)

    @log
    def unselect_all(self):
        self.albums_selected = []
        self._toggle_all_selection(False)
Beispiel #27
0
class SearchView(BaseView):

    __gsignals__ = {
        'no-music-found': (GObject.SignalFlags.RUN_FIRST, None, ())
    }

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

    @log
    def __init__(self, window, player):
        super().__init__('search', None, window, Gd.MainViewType.LIST)

        self._add_list_renderers()
        self.player = player
        self._head_iters = [None, None, None, None]
        self._filter_model = None

        self.previous_view = None
        self.connect('no-music-found', self._no_music_found_callback)

        self._albums_selected = []
        self._albums = {}
        self._albums_index = 0
        self._album_widget = AlbumWidget(player, self)
        self.add(self._album_widget)

        self._artists_albums_selected = []
        self._artists_albums_index = 0
        self._artists = {}
        self._artist_albums_widget = None

        self._view.get_generic_view().set_show_expanders(False)
        self._items_selected = []
        self._items_selected_callback = None

        self._items_found = None

    @log
    def _no_music_found_callback(self, view):
        # FIXME: call into private members
        self._window._stack.set_visible_child_name('emptysearch')
        emptysearch = self._window._stack.get_child_by_name('emptysearch')
        emptysearch._artist_albums_widget = self._artist_albums_widget

    @log
    def _back_button_clicked(self, widget, data=None):
        self._header_bar.searchbar.reveal(True, False)

        if self.get_visible_child() == self._artist_albums_widget:
            self._artist_albums_widget.destroy()
            self._artist_albums_widget = None
        elif self.get_visible_child() == self._grid:
            self._window.views[View.ALBUM].set_visible_child(
                self._window.views[View.ALBUM]._grid)

        self.set_visible_child(self._grid)
        self._window.toolbar.set_state(ToolbarState.MAIN)

    @log
    def _on_item_activated(self, widget, id, path):
        if self._star_handler.star_renderer_click:
            self._star_handler.star_renderer_click = False
            return

        try:
            child_path = self._filter_model.convert_path_to_child_path(path)
        except TypeError:
            return

        _iter = self.model.get_iter(child_path)
        if self.model[_iter][11] == 'album':
            title = self.model[_iter][2]
            artist = self.model[_iter][3]
            item = self.model[_iter][5]

            self._album_widget.update(
                artist, title, item, self._header_bar, self._selection_toolbar)
            self._header_bar.set_state(ToolbarState.SEARCH_VIEW)

            self._header_bar.header_bar.set_title(title)
            self._header_bar.header_bar.sub_title = artist
            self.set_visible_child(self._album_widget)
            self._header_bar.searchbar.reveal(False)
        elif self.model[_iter][11] == 'artist':
            artist = self.model[_iter][2]
            albums = self._artists[artist.casefold()]['albums']

            self._artist_albums_widget = ArtistAlbumsWidget(
                artist, albums, self.player, self._header_bar,
                self._selection_toolbar, self._window, True)
            self.add(self._artist_albums_widget)
            self._artist_albums_widget.show()

            self._header_bar.set_state(ToolbarState.SEARCH_VIEW)
            self._header_bar.header_bar.set_title(artist)
            self.set_visible_child(self._artist_albums_widget)
            self._header_bar.searchbar.reveal(False)
        elif self.model[_iter][11] == 'song':
            if self.model[_iter][12] != DiscoveryStatus.FAILED:
                c_iter = self._songs_model.convert_child_iter_to_iter(_iter)[1]
                self.player.set_playlist(
                    'Search Results', None, self._songs_model, c_iter, 5, 12)
                self.player.set_playing(True)
        else:  # Headers
            if self._view.get_generic_view().row_expanded(path):
                self._view.get_generic_view().collapse_row(path)
            else:
                self._view.get_generic_view().expand_row(path, False)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        if (self._artist_albums_widget is not None
                and self.get_visible_child() == self._artist_albums_widget):
            self._artist_albums_widget.set_selection_mode(
                self._header_bar.selection_mode)

    @log
    def _add_search_item(self, source, param, item, remaining=0, data=None):
        if not item:
            if (grilo._search_callback_counter == 0
                    and grilo.search_source):
                self.emit('no-music-found')
            return

        if data != self.model:
            return

        artist = utils.get_artist_name(item)
        album = utils.get_album_title(item)

        key = '%s-%s' % (artist, album)
        if key not in self._albums:
            self._albums[key] = Grl.Media()
            self._albums[key].set_title(album)
            self._albums[key].add_artist(artist)
            self._albums[key].set_source(source.get_id())
            self._albums[key].songs = []
            self._add_item(
                source, None, self._albums[key], 0, [self.model, 'album'])
            self._add_item(
                source, None, self._albums[key], 0, [self.model, 'artist'])

        self._albums[key].songs.append(item)
        self._add_item(source, None, item, 0, [self.model, 'song'])

    @log
    def _retrieval_finished(self, klass, model, _iter):
        if not model[_iter][13]:
            return

        model[_iter][13] = klass.surface

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if data is None:
            return

        model, category = data

        self._items_found = (
            self.model.iter_n_children(self._head_iters[0])
            + self.model.iter_n_children(self._head_iters[1])
            + self.model.iter_n_children(self._head_iters[2])
            + self.model.iter_n_children(self._head_iters[3])
        )

        if (category == 'song'
                and self._items_found == 0
                and remaining == 0):
            if grilo.search_source:
                self.emit('no-music-found')

        # We need to remember the view before the search view
        emptysearchview = self._window.views[View.EMPTY_SEARCH]
        if (self._window.curr_view != emptysearchview
                and self._window.prev_view != emptysearchview):
            self.previous_view = self._window.prev_view

        if remaining == 0:
            self._window.notifications_popup.pop_loading()
            self._view.show()

        if not item or model != self.model:
            return

        self._offset += 1
        title = utils.get_media_title(item)
        item.set_title(title)
        artist = utils.get_artist_name(item)

        group = 3
        try:
            group = {'album': 0, 'artist': 1, 'song': 2}[category]
        except:
            pass

        _iter = None
        if category == 'album':
            _iter = self.model.insert_with_values(
                self._head_iters[group], -1, [0, 2, 3, 5, 9, 11],
                [str(item.get_id()), title, artist, item, 2,
                 category])
        elif category == 'song':
            # FIXME: source specific hack
            if source.get_id() != 'grl-tracker-source':
                fav = 2
            else:
                fav = item.get_favourite()
            _iter = self.model.insert_with_values(
                self._head_iters[group], -1, [0, 2, 3, 5, 9, 11],
                [str(item.get_id()), title, artist, item, fav,
                 category])
        else:
            if not artist.casefold() in self._artists:
                _iter = self.model.insert_with_values(
                    self._head_iters[group], -1, [0, 2, 5, 9, 11],
                    [str(item.get_id()), artist, item, 2,
                     category])
                self._artists[artist.casefold()] = {
                    'iter': _iter,
                    'albums': []
                }
            self._artists[artist.casefold()]['albums'].append(item)

        # FIXME: Figure out why iter can be None here, seems illogical.
        if _iter is not None:
            scale = self._view.get_scale_factor()
            art = Art(Art.Size.SMALL, item, scale)
            self.model[_iter][13] = art.surface
            art.connect(
                'finished', self._retrieval_finished, self.model, _iter)
            art.lookup()

        if self.model.iter_n_children(self._head_iters[group]) == 1:
            path = self.model.get_path(self._head_iters[group])
            path = self._filter_model.convert_child_path_to_path(path)
            self._view.get_generic_view().expand_row(path, False)

    @log
    def _add_list_renderers(self):
        list_widget = self._view.get_generic_view()
        list_widget.set_halign(Gtk.Align.CENTER)
        list_widget.set_size_request(530, -1)
        cols = list_widget.get_columns()

        title_renderer = Gtk.CellRendererText(
            xpad=12, xalign=0.0, yalign=0.5, height=32,
            ellipsize=Pango.EllipsizeMode.END, weight=Pango.Weight.BOLD)
        list_widget.add_renderer(
            title_renderer, self._on_list_widget_title_render, None)
        cols[0].add_attribute(title_renderer, 'text', 2)

        # Add our own surface renderer, instead of the one provided by
        # Gd. This avoids us having to set the model to a cairo.Surface
        # which is currently not a working solution in pygobject.
        # https://gitlab.gnome.org/GNOME/pygobject/issues/155
        pixbuf_renderer = Gtk.CellRendererPixbuf(
            xalign=0.5, yalign=0.5, xpad=12, ypad=2)
        list_widget.add_renderer(
            pixbuf_renderer, self._on_list_widget_pixbuf_renderer, None)
        cols[0].add_attribute(pixbuf_renderer, 'surface', 13)

        self._star_handler.add_star_renderers(list_widget, cols[0])
        cells = cols[0].get_cells()

        # With the bugfix in libgd 9117650bda, the search results
        # stopped aligning at the top. With the artists results not
        # having a second line of text, this looks off.
        # Revert to old behaviour by forcing the alignment to be top.
        # FIXME: Revisit this when rewriting the searchview.
        gd_twolines_renderer = cells[2]
        gd_twolines_renderer.props.yalign = 0

        cols[0].reorder(cells[0], -1)
        cols[0].reorder(cells[4], 1)
        cols[0].set_cell_data_func(
            cells[0], self._on_list_widget_selection_render, None)

    @log
    def _on_list_widget_pixbuf_renderer(self, col, cell, model, _iter, data):
        if not model[_iter][13]:
            return

        cell.set_property("surface", model[_iter][13])

    @log
    def _on_list_widget_selection_render(self, col, cell, model, _iter, data):
        if (self._view.get_selection_mode()
                and model.iter_parent(_iter) is not None):
            cell.set_visible(True)
        else:
            cell.set_visible(False)

    @log
    def _on_list_widget_title_render(self, col, cell, model, _iter, data):
        cells = col.get_cells()
        cells[0].set_visible(False)
        cells[1].set_visible(model.iter_parent(_iter) is not None)
        cells[2].set_visible(model.iter_parent(_iter) is not None)
        cells[3].set_visible(model.iter_parent(_iter) is None)

    @log
    def populate(self):
        self._init = True
        self._window.notifications_popup.push_loading()
        self._header_bar.set_state(ToolbarState.MAIN)

    @log
    def get_selected_songs(self, callback):
        if self.get_visible_child() == self._album_widget:
            callback(self._album_widget.view.get_selected_items())
        elif self.get_visible_child() == self._artist_albums_widget:
            items = []
            # FIXME: calling into private model
            for row in self._artist_albums_widget._model:
                if row[6]:
                    items.append(row[5])
            callback(items)
        else:
            self._items_selected = []
            self._items_selected_callback = callback
            self._get_selected_albums()

    @log
    def _get_selected_albums(self):
        paths = [
            self._filter_model.convert_path_to_child_path(path)
            for path in self._view.get_selection()]

        self._albums_selected = [
            self.model[child_path][5]
            for child_path in paths
            if self.model[child_path][11] == 'album']

        if len(self._albums_selected):
            self._get_selected_albums_songs()
        else:
            self._get_selected_artists()

    @log
    def _get_selected_albums_songs(self):
        grilo.populate_album_songs(
            self._albums_selected[self._albums_index],
            self._add_selected_albums_songs)
        self._albums_index += 1

    @log
    def _add_selected_albums_songs(
            self, source, param, item, remaining=0, data=None):
        if item:
            self._items_selected.append(item)
        if remaining == 0:
            if self._albums_index < len(self._albums_selected):
                self._get_selected_albums_songs()
            else:
                self._get_selected_artists()

    @log
    def _get_selected_artists(self):
        artists_selected = [
            self._artists[self.model[child_path][2].casefold()]
            for child_path in [
                self._filter_model.convert_path_to_child_path(path)
                for path in self._view.get_selection()]
            if self.model[child_path][11] == 'artist']

        self._artists_albums_selected = []
        for artist in artists_selected:
            self._artists_albums_selected.extend(artist['albums'])

        if len(self._artists_albums_selected):
            self._get_selected_artists_albums_songs()
        else:
            self._get_selected_songs()

    @log
    def _get_selected_artists_albums_songs(self):
        grilo.populate_album_songs(
            self._artists_albums_selected[self._artists_albums_index],
            self._add_selected_artists_albums_songs)
        self._artists_albums_index += 1

    @log
    def _add_selected_artists_albums_songs(
            self, source, param, item, remaining=0, data=None):
        if item:
            self._items_selected.append(item)
        if remaining == 0:
            artist_albums = len(self._artists_albums_selected)
            if self._artists_albums_index < artist_albums:
                self._get_selected_artists_albums_songs()
            else:
                self._get_selected_songs()

    @log
    def _get_selected_songs(self):
        self._items_selected.extend([
            self.model[child_path][5]
            for child_path in [
                self._filter_model.convert_path_to_child_path(path)
                for path in self._view.get_selection()]
            if self.model[child_path][11] == 'song'])
        self._items_selected_callback(self._items_selected)

    @log
    def _filter_visible_func(self, model, _iter, data=None):
        top_level = model.iter_parent(_iter) is None
        visible = (not top_level or model.iter_has_child(_iter))

        return visible

    @log
    def set_search_text(self, search_term, fields_filter):
        query_matcher = {
            'album': {
                'search_all': Query.get_albums_with_any_match,
                'search_artist': Query.get_albums_with_artist_match,
                'search_composer': Query.get_albums_with_composer_match,
                'search_album': Query.get_albums_with_album_match,
                'search_track': Query.get_albums_with_track_match,
            },
            'artist': {
                'search_all': Query.get_artists_with_any_match,
                'search_artist': Query.get_artists_with_artist_match,
                'search_composer': Query.get_artists_with_composer_match,
                'search_album': Query.get_artists_with_album_match,
                'search_track': Query.get_artists_with_track_match,
            },
            'song': {
                'search_all': Query.get_songs_with_any_match,
                'search_artist': Query.get_songs_with_artist_match,
                'search_composer': Query.get_songs_with_composer_match,
                'search_album': Query.get_songs_with_album_match,
                'search_track': Query.get_songs_with_track_match,
            },
        }

        self.model = Gtk.TreeStore(
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,
            GObject.TYPE_STRING,    # item title or header text
            GObject.TYPE_STRING,    # artist for albums and songs
            GdkPixbuf.Pixbuf,       # Gd placeholder album art
            GObject.TYPE_OBJECT,    # item
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_INT,
            GObject.TYPE_STRING,
            GObject.TYPE_INT,
            GObject.TYPE_BOOLEAN,
            GObject.TYPE_STRING,    # type
            GObject.TYPE_INT,
            object                  # album art surface
        )

        self._filter_model = self.model.filter_new(None)
        self._filter_model.set_visible_func(self._filter_visible_func)
        self._view.set_model(self._filter_model)

        self._albums = {}
        self._artists = {}

        if search_term == "":
            return

        albums_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Albums"), 2])
        artists_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Artists"), 2])
        songs_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Songs"), 2])
        playlists_iter = self.model.insert_with_values(
            None, -1, [2, 9], [_("Playlists"), 2])

        self._head_iters = [
            albums_iter,
            artists_iter,
            songs_iter,
            playlists_iter
        ]

        self._songs_model = self.model.filter_new(
            self.model.get_path(songs_iter))

        # Use queries for Tracker
        if (not grilo.search_source
                or grilo.search_source.get_id() == 'grl-tracker-source'):
            for category in ('album', 'artist', 'song'):
                query = query_matcher[category][fields_filter](search_term)
                grilo.populate_custom_query(
                    query, self._add_item, -1, [self.model, category])
        if (not grilo.search_source
                or grilo.search_source.get_id() != 'grl-tracker-source'):
            # nope, can't do - reverting to Search
            grilo.search(search_term, self._add_search_item, self.model)
Beispiel #28
0
class SearchView(BaseView):

    search_state = GObject.Property(type=int, default=Search.State.NONE)

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

    @log
    def __init__(self, window, player):
        self._coremodel = window._app.props.coremodel
        self._model = self._coremodel.props.songs_search
        self._album_model = self._coremodel.props.albums_search
        self._artist_model = self._coremodel.props.artists_search
        super().__init__('search', None, window)

        self.player = player

        self.previous_view = None

        self._album_widget = AlbumWidget(player, self)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self._artist_albums_widget = None

        self._search_mode_active = False
        # self.connect("notify::search-state", self._on_search_state_changed)

    @log
    def _setup_view(self):
        view_container = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
        self._box.pack_start(view_container, True, True, 0)

        self._songs_listbox = Gtk.ListBox()
        self._songs_listbox.bind_model(self._model, self._create_song_widget)

        self._album_flowbox = Gtk.FlowBox(
            homogeneous=True,
            hexpand=True,
            halign=Gtk.Align.FILL,
            valign=Gtk.Align.START,
            selection_mode=Gtk.SelectionMode.NONE,
            margin=18,
            row_spacing=12,
            column_spacing=6,
            min_children_per_line=1,
            max_children_per_line=20,
            visible=True)
        self._album_flowbox.get_style_context().add_class('content-view')
        self._album_flowbox.bind_model(self._album_model,
                                       self._create_album_widget)
        self._album_flowbox.connect("child-activated",
                                    self._on_album_activated)

        self._artist_listbox = Gtk.ListBox()
        self._artist_listbox.bind_model(self._artist_model,
                                        self._create_artist_widget)

        self._all_results_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self._all_results_box.pack_start(self._album_flowbox, True, True, 0)
        self._all_results_box.pack_start(self._artist_listbox, True, True, 0)
        self._all_results_box.pack_start(self._songs_listbox, True, True, 0)

        # self._ctrl = Gtk.GestureMultiPress().new(self._view)
        # self._ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
        # self._ctrl.connect("released", self._on_view_clicked)

        view_container.add(self._all_results_box)

        self._box.show_all()

    def _create_song_widget(self, coresong):
        song_widget = SongWidget(coresong)

        self.bind_property(
            "selection-mode", song_widget, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL
            | GObject.BindingFlags.SYNC_CREATE)

        song_widget.connect('button-release-event', self._song_activated)

        song_widget.show_all()

        return song_widget

    def _create_album_widget(self, corealbum):
        album_widget = AlbumCover(corealbum)

        self.bind_property(
            "selection-mode", album_widget, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        # NOTE: Adding SYNC_CREATE here will trigger all the nested
        # models to be created. This will slow down initial start,
        # but will improve initial 'selecte all' speed.
        album_widget.bind_property("selected", corealbum, "selected",
                                   GObject.BindingFlags.BIDIRECTIONAL)

        return album_widget

    def _create_artist_widget(self, coreartist):
        artist_tile = ArtistTile(coreartist)
        artist_tile.props.text = coreartist.props.artist
        artist_tile.connect('button-release-event', self._artist_activated)

        self.bind_property(
            "selection-mode", artist_tile, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        return artist_tile

    def _song_activated(self, widget, event):
        mod_mask = Gtk.accelerator_get_default_mod_mask()
        if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK
                and not self.props.selection_mode):
            self.props.selection_mode = True
            return

        (_, button) = event.get_button()
        if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode):
            # self.emit('song-activated', widget)

            self._coremodel.set_playlist_model(
                PlayerPlaylist.Type.SEARCH_RESULT, self._model)
            self.player.play(widget.props.coresong)

        # FIXME: Need to ignore the event from the checkbox.
        # if self.props.selection_mode:
        #     widget.props.selected = not widget.props.selected

        return True

    def _on_album_activated(self, widget, child, user_data=None):
        corealbum = child.props.corealbum
        if self.props.selection_mode:
            return

        # Update and display the album widget if not in selection mode
        self._album_widget.update(corealbum)

        self._headerbar.props.state = HeaderBar.State.SEARCH
        self._headerbar.props.title = corealbum.props.title
        self._headerbar.props.subtitle = corealbum.props.artist
        self.props.search_mode_active = False

        self.set_visible_child(self._album_widget)

    def _artist_activated(self, widget, event):
        coreartist = widget.coreartist

        mod_mask = Gtk.accelerator_get_default_mod_mask()
        if ((event.get_state() & mod_mask) == Gdk.ModifierType.CONTROL_MASK
                and not self.props.selection_mode):
            self.props.selection_mode = True
            return

        (_, button) = event.get_button()
        if (button == Gdk.BUTTON_PRIMARY and not self.props.selection_mode):
            # self.emit('song-activated', widget)

            self._artist_albums_widget = ArtistAlbumsWidget(
                coreartist, self.player, self._window, False)
            self.add(self._artist_albums_widget)
            self._artist_albums_widget.show()

            self.bind_property('selection-mode', self._artist_albums_widget,
                               'selection-mode',
                               GObject.BindingFlags.BIDIRECTIONAL)

            self._headerbar.props.state = HeaderBar.State.SEARCH
            self._headerbar.props.title = coreartist.artist
            self._headerbar.props.subtitle = None
            self.set_visible_child(self._artist_albums_widget)
            self.props.search_mode_active = False

        # FIXME: Need to ignore the event from the checkbox.
        # if self.props.selection_mode:
        #     widget.props.selected = not widget.props.selected

        return True

    def _child_select(self, child, value):
        widget = child.get_child()
        widget.props.selected = value

    def _select_all(self, value):
        with self._model.freeze_notify():

            def song_select(child):
                song_widget = child.get_child()
                song_widget.props.selected = value

            def album_select(child):
                child.props.selected = value

            def artist_select(child):
                artist_widget = child.get_child()
                artist_widget.props.selected = value

            self._songs_listbox.foreach(song_select)
            self._album_flowbox.foreach(album_select)
            self._artist_listbox.foreach(artist_select)

    def select_all(self):
        self._select_all(True)

    def unselect_all(self):
        self._select_all(False)

    @log
    def _back_button_clicked(self, widget, data=None):
        if self.get_visible_child() == self._artist_albums_widget:
            self._artist_albums_widget.destroy()
            self._artist_albums_widget = None
        elif self.get_visible_child() == self._grid:
            self._window.views[View.ALBUM].set_visible_child(
                self._window.views[View.ALBUM]._grid)

        self.set_visible_child(self._grid)
        self.props.search_mode_active = True
        self._headerbar.props.state = HeaderBar.State.MAIN

    @log
    def _on_view_clicked(self, gesture, n_press, x, y):
        """Ctrl+click on self._view triggers selection mode."""
        _, state = Gtk.get_current_event_state()
        modifiers = Gtk.accelerator_get_default_mod_mask()
        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
                and not self.props.selection_mode):
            self.props.selection_mode = True

        if (self.selection_mode
                and not self._star_handler.star_renderer_click):
            path, col, cell_x, cell_y = self._view.get_path_at_pos(x, y)
            iter_ = self.model.get_iter(path)
            self.model[iter_][6] = not self.model[iter_][6]
            selected_iters = self._get_selected_iters()

            self.props.selected_items_count = len(selected_iters)

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        super()._on_selection_mode_changed(widget, data)

    @log
    def _on_search_state_changed(self, klass, param):
        # If a search is triggered when selection mode is activated,
        # reset the number of selected items.
        if (self.props.selection_mode
                and self.props.search_state != Search.State.NONE):
            self.props.selected_items_count = 0

    @GObject.Property(type=bool, default=False)
    def search_mode_active(self):
        """Get search mode status.

        :returns: the search mode status
        :rtype: bool
        """
        return self._search_mode_active

    @search_mode_active.setter
    def search_mode_active(self, value):
        """Set search mode status.

        :param bool mode: new search mode
        """
        # FIXME: search_mode_active should not change search_state.
        # This is necessary because Search state cannot interact with
        # the child views.
        self._search_mode_active = value
        if (not self._search_mode_active
                and self.get_visible_child() == self._grid):
            self.props.search_state = Search.State.NONE

    @log
    def _populate(self, data=None):
        self._init = True
        self._headerbar.props.state = HeaderBar.State.MAIN
Beispiel #29
0
class AlbumsView(BaseView):

    search_mode_active = GObject.Property(type=bool, default=False)

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

    @log
    def __init__(self, window, player):
        super().__init__('albums', _("Albums"), window)

        self.player = player

        self._album_widget = AlbumWidget(player)
        self._album_widget.bind_property(
            "selection-mode", self, "selection-mode",
            GObject.BindingFlags.BIDIRECTIONAL)
        self._album_widget.bind_property(
            "selected-items-count", self, "selected-items-count")

        self.add(self._album_widget)
        self.albums_selected = []
        self.all_items = []
        self.items_selected = []
        self.items_selected_callback = None

        self.connect(
            "notify::search-mode-active", self._on_search_mode_changed)

    @log
    def _on_changes_pending(self, data=None):
        if (self._init and not self.props.selection_mode):
            self._offset = 0
            self._populate()
            grilo.changes_pending['Albums'] = False

    @log
    def _on_selection_mode_changed(self, widget, data=None):
        super()._on_selection_mode_changed(widget, data)

        if (not self.props.selection_mode
                and grilo.changes_pending['Albums']):
            self._on_changes_pending()

    @log
    def _on_search_mode_changed(self, klass, param):
        if (not self.props.search_mode_active
                and self._headerbar.props.stack.props.visible_child == self
                and self.get_visible_child() == self._album_widget):
            self._set_album_headerbar(self._album_widget.props.album)

    @log
    def _setup_view(self):
        self._view = Gtk.FlowBox(
            homogeneous=True, hexpand=True, halign=Gtk.Align.FILL,
            valign=Gtk.Align.START, selection_mode=Gtk.SelectionMode.NONE,
            margin=18, row_spacing=12, column_spacing=6,
            min_children_per_line=1, max_children_per_line=20)

        self._view.get_style_context().add_class('content-view')
        self._view.connect('child-activated', self._on_child_activated)

        scrolledwin = Gtk.ScrolledWindow()
        scrolledwin.add(self._view)
        scrolledwin.show()

        self._box.add(scrolledwin)

    @log
    def _back_button_clicked(self, widget, data=None):
        self._headerbar.state = HeaderBar.State.MAIN
        self.set_visible_child(self._grid)

    @log
    def _on_child_activated(self, widget, child, user_data=None):
        if self.props.selection_mode:
            return

        item = child.props.media
        # Update and display the album widget if not in selection mode
        self._album_widget.update(item)

        self._set_album_headerbar(item)
        self.set_visible_child(self._album_widget)

    @log
    def _set_album_headerbar(self, album):
        self._headerbar.props.state = HeaderBar.State.CHILD
        self._headerbar.props.title = utils.get_album_title(album)
        self._headerbar.props.subtitle = utils.get_artist_name(album)

    @log
    def _populate(self, data=None):
        self._window.notifications_popup.push_loading()
        grilo.populate_albums(self._offset, self._add_item)
        self._init = True

    @log
    def get_selected_songs(self, callback):
        # FIXME: we call into private objects with full knowledge of
        # what is there
        if self._headerbar.props.state == HeaderBar.State.CHILD:
            callback(self._album_widget._disc_listbox.get_selected_items())
        else:
            self.items_selected = []
            self.items_selected_callback = callback
            self.albums_index = 0
            if len(self.albums_selected):
                self._get_selected_album_songs()

    @log
    def _add_item(self, source, param, item, remaining=0, data=None):
        if item:
            # Store all items to optimize 'Select All' action
            self.all_items.append(item)

            # Add to the flowbox
            child = self._create_album_item(item)
            self._view.add(child)
            self._offset += 1
        elif remaining == 0:
            self._view.show()
            self._window.notifications_popup.pop_loading()
            self._init = False

    def _create_album_item(self, item):
        child = AlbumCover(item)

        child.connect('notify::selected', self._on_selection_changed)

        self.bind_property(
            'selection-mode', child, 'selection-mode',
            GObject.BindingFlags.BIDIRECTIONAL)

        return child

    @log
    def _on_selection_changed(self, child, data=None):
        if (child.props.selected
                and child.props.media not in self.albums_selected):
            self.albums_selected.append(child.props.media)
        elif (not child.props.selected
                and child.props.media in self.albums_selected):
            self.albums_selected.remove(child.props.media)

        self.props.selected_items_count = len(self.albums_selected)

    @log
    def _get_selected_album_songs(self):
        grilo.populate_album_songs(
            self.albums_selected[self.albums_index],
            self._add_selected_item)
        self.albums_index += 1

    @log
    def _add_selected_item(self, source, param, item, remaining=0, data=None):
        if item:
            self.items_selected.append(item)
        if remaining == 0:
            if self.albums_index < self.props.selected_items_count:
                self._get_selected_album_songs()
            else:
                self.items_selected_callback(self.items_selected)

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        for child in self._view.get_children():
            child.props.selected = selected

    @log
    def select_all(self):
        self.albums_selected = list(self.all_items)
        self._toggle_all_selection(True)

    @log
    def unselect_all(self):
        self.albums_selected = []
        self._toggle_all_selection(False)
Beispiel #30
0
class AlbumsView(Gtk.Stack):
    """Gridlike view of all albums

    Album activation switches to AlbumWidget.
    """

    __gtype_name__ = "AlbumsView"

    search_mode_active = GObject.Property(type=bool, default=False)
    selection_mode = GObject.Property(type=bool, default=False)
    title = GObject.Property(type=str,
                             default=_("Albums"),
                             flags=GObject.ParamFlags.READABLE)

    _scrolled_window = Gtk.Template.Child()
    _flowbox = Gtk.Template.Child()
    _flowbox_long_press = Gtk.Template.Child()

    def __init__(self, application):
        """Initialize AlbumsView

        :param application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        self.props.name = "albums"

        self._application = application
        self._window = application.props.window
        self._headerbar = self._window._headerbar
        self._adjustment_timeout_id = 0
        self._viewport = self._scrolled_window.get_child()
        self._widget_counter = 1
        self._ctrl_hold = False

        model = self._application.props.coremodel.props.albums_sort
        self._flowbox.bind_model(model, self._create_widget)
        self._flowbox.set_hadjustment(self._scrolled_window.get_hadjustment())
        self._flowbox.set_vadjustment(self._scrolled_window.get_vadjustment())
        self._flowbox.connect("child-activated", self._on_child_activated)

        self.bind_property("selection-mode", self._window, "selection-mode",
                           GObject.BindingFlags.DEFAULT)

        self._window.connect("notify::selection-mode",
                             self._on_selection_mode_changed)

        self._album_widget = AlbumWidget(self._application)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self.connect("notify::search-mode-active",
                     self._on_search_mode_changed)

        self._scrolled_window.props.vadjustment.connect(
            "value-changed", self._on_vadjustment_changed)
        self._scrolled_window.props.vadjustment.connect(
            "changed", self._on_vadjustment_changed)

    def _on_vadjustment_changed(self, adjustment):
        if self._adjustment_timeout_id != 0:
            GLib.source_remove(self._adjustment_timeout_id)
            self._adjustment_timeout_id = 0

        self._adjustment_timeout_id = GLib.timeout_add(
            200,
            self._retrieve_covers,
            adjustment.props.value,
            priority=GLib.PRIORITY_LOW)

    def _retrieve_covers(self, old_adjustment):
        adjustment = self._scrolled_window.props.vadjustment.props.value

        if old_adjustment != adjustment:
            return GLib.SOURCE_CONTINUE

        first_cover = self._flowbox.get_child_at_index(0)
        if first_cover is None:
            return GLib.SOURCE_REMOVE

        cover_size, _ = first_cover.get_allocated_size()
        if cover_size.width == 0 or cover_size.height == 0:
            return GLib.SOURCE_REMOVE

        viewport_size, _ = self._viewport.get_allocated_size()

        h_space = self._flowbox.get_column_spacing()
        v_space = self._flowbox.get_row_spacing()
        nr_cols = ((viewport_size.width + h_space) //
                   (cover_size.width + h_space))

        top_left_cover = self._flowbox.get_child_at_index(
            nr_cols * (adjustment // (cover_size.height + v_space)))

        covers_col = math.ceil(viewport_size.width / cover_size.width)
        covers_row = math.ceil(viewport_size.height / cover_size.height)

        children = self._flowbox.get_children()
        retrieve_list = []
        for i, albumcover in enumerate(children):
            if top_left_cover == albumcover:
                retrieve_covers = covers_row * covers_col
                retrieve_list = children[i:i + retrieve_covers]
                break

        for albumcover in retrieve_list:
            albumcover.retrieve()

        self._adjustment_timeout_id = 0

        return GLib.SOURCE_REMOVE

    def _on_selection_mode_changed(self, widget, data=None):
        selection_mode = self._window.props.selection_mode
        if (selection_mode == self.props.selection_mode
                or self.get_parent().get_visible_child() != self):
            return

        self.props.selection_mode = selection_mode
        if not self.props.selection_mode:
            self.deselect_all()

    def _on_search_mode_changed(self, klass, param):
        if (not self.props.search_mode_active
                and self._headerbar.props.stack.props.visible_child == self
                and self.get_visible_child() == self._album_widget):
            self._set_album_headerbar(self._album_widget.props.album)

    def _create_widget(self, corealbum):
        album_widget = AlbumCover(corealbum)

        self.bind_property(
            "selection-mode", album_widget, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        # NOTE: Adding SYNC_CREATE here will trigger all the nested
        # models to be created. This will slow down initial start,
        # but will improve initial 'select all' speed.
        album_widget.bind_property("selected", corealbum, "selected",
                                   GObject.BindingFlags.BIDIRECTIONAL)

        GLib.timeout_add(self._widget_counter * 250,
                         album_widget.retrieve,
                         priority=GLib.PRIORITY_LOW)
        self._widget_counter = self._widget_counter + 1

        return album_widget

    def _back_button_clicked(self, widget, data=None):
        self._headerbar.state = HeaderBar.State.MAIN
        self.props.visible_child = self._scrolled_window

    def _on_child_activated(self, widget, child, user_data=None):
        corealbum = child.props.corealbum
        if self.props.selection_mode:
            return

        # Update and display the album widget if not in selection mode
        self._album_widget.update(corealbum)

        self._set_album_headerbar(corealbum)
        self.set_visible_child(self._album_widget)

    def _set_album_headerbar(self, corealbum):
        self._headerbar.props.state = HeaderBar.State.CHILD
        self._headerbar.props.title = corealbum.props.title
        self._headerbar.props.subtitle = corealbum.props.artist

    @Gtk.Template.Callback()
    def _on_flowbox_press_begin(self, gesture, sequence):
        event = gesture.get_last_event(sequence)
        ok, state = event.get_state()
        if ((ok is True and state == Gdk.ModifierType.CONTROL_MASK)
                or self.props.selection_mode is True):
            self._flowbox.props.selection_mode = Gtk.SelectionMode.MULTIPLE
            if state == Gdk.ModifierType.CONTROL_MASK:
                self._ctrl_hold = True

    @Gtk.Template.Callback()
    def _on_flowbox_press_cancel(self, gesture, sequence):
        self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE

    @Gtk.Template.Callback()
    def _on_selected_children_changed(self, flowbox):
        if self._flowbox.props.selection_mode == Gtk.SelectionMode.NONE:
            return

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

        rubberband_selection = len(self._flowbox.get_selected_children()) > 1
        with self._application.props.coreselection.freeze_notify():
            if (rubberband_selection and not self._ctrl_hold):
                self.deselect_all()
            for child in self._flowbox.get_selected_children():
                if (self._ctrl_hold is True or not rubberband_selection):
                    child.props.selected = not child.props.selected
                else:
                    child.props.selected = True

        self._ctrl_hold = False
        self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE

    def _toggle_all_selection(self, selected):
        """Selects or deselects all items.
        """
        with self._application.props.coreselection.freeze_notify():
            if self.get_visible_child() == self._album_widget:
                if selected is True:
                    self._album_widget.select_all()
                else:
                    self._album_widget.deselect_all()
            else:
                for child in self._flowbox.get_children():
                    child.props.selected = selected

    def select_all(self):
        self._toggle_all_selection(True)

    def deselect_all(self):
        self._toggle_all_selection(False)
Beispiel #31
0
class AlbumsView(Gtk.Stack):
    """Gridlike view of all albums

    Album activation switches to AlbumWidget.
    """

    __gtype_name__ = "AlbumsView"

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

    _all_albums = Gtk.Template.Child()
    _flowbox = Gtk.Template.Child()

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

    def __init__(self, application, player=None):
        """Initialize AlbumsView

        :param application: The Application object
        """
        super().__init__(transition_type=Gtk.StackTransitionType.CROSSFADE)

        # FIXME: Make these properties.
        self.name = "albums"
        self.title = _("Albums")

        self._window = application.props.window
        self._headerbar = self._window._headerbar

        model = self._window._app.props.coremodel.props.albums_sort
        self._flowbox.bind_model(model, self._create_widget)
        self._flowbox.connect("child-activated", self._on_child_activated)

        self.connect("notify::selection-mode", self._on_selection_mode_changed)

        self.bind_property('selection-mode', self._window, 'selection-mode',
                           GObject.BindingFlags.BIDIRECTIONAL)

        self._album_widget = AlbumWidget(application.props.player, self)
        self._album_widget.bind_property("selection-mode", self,
                                         "selection-mode",
                                         GObject.BindingFlags.BIDIRECTIONAL)

        self.add(self._album_widget)

        self.connect("notify::search-mode-active",
                     self._on_search_mode_changed)

        self.show_all()

    def _on_selection_mode_changed(self, widget, data=None):
        if not self.props.selection_mode:
            self.unselect_all()

    def _on_search_mode_changed(self, klass, param):
        if (not self.props.search_mode_active
                and self._headerbar.props.stack.props.visible_child == self
                and self.get_visible_child() == self._album_widget):
            self._set_album_headerbar(self._album_widget.props.album)

    def _create_widget(self, corealbum):
        album_widget = AlbumCover(corealbum)

        self.bind_property(
            "selection-mode", album_widget, "selection-mode",
            GObject.BindingFlags.SYNC_CREATE
            | GObject.BindingFlags.BIDIRECTIONAL)

        # NOTE: Adding SYNC_CREATE here will trigger all the nested
        # models to be created. This will slow down initial start,
        # but will improve initial 'selecte all' speed.
        album_widget.bind_property("selected", corealbum, "selected",
                                   GObject.BindingFlags.BIDIRECTIONAL)

        return album_widget

    def _back_button_clicked(self, widget, data=None):
        self._headerbar.state = HeaderBar.State.MAIN
        self.props.visible_child = self._all_albums

    def _on_child_activated(self, widget, child, user_data=None):
        corealbum = child.props.corealbum
        if self.props.selection_mode:
            return

        # Update and display the album widget if not in selection mode
        self._album_widget.update(corealbum)

        self._set_album_headerbar(corealbum)
        self.set_visible_child(self._album_widget)

    def _set_album_headerbar(self, corealbum):
        self._headerbar.props.state = HeaderBar.State.CHILD
        self._headerbar.props.title = corealbum.props.title
        self._headerbar.props.subtitle = corealbum.props.artist

    def _toggle_all_selection(self, selected):
        """
        Selects or unselects all items without sending the notify::active
        signal for performance purposes.
        """
        with self._window._app.props.coreselection.freeze_notify():
            for child in self._flowbox.get_children():
                child.props.selected = selected
                child.props.corealbum.props.selected = selected

    def select_all(self):
        self._toggle_all_selection(True)

    def unselect_all(self):
        self._toggle_all_selection(False)