def __init__(self, artist_id=None):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect("destroy", self._on_destroy)
     self._artist_id = artist_id
Exemple #2
0
    def __init__(self):

        # Try to update db on start, will be done after list one poplating
        # finished
        self._need_to_update_db = True
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Navigation.DEVICES
        self._show_genres = Objects.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        self._list_one_restore = Navigation.POPULARS
        self._list_two_restore = Navigation.NONE
        if Objects.settings.get_value('save-state'):
            self._restore_view_state()

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Objects.playlists.connect("playlists-changed", self.update_lists)
Exemple #3
0
 def __init__(self):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect('destroy', self._on_destroy)
     self._current = (Type.NONE, Type.NONE)
Exemple #4
0
    def __init__(self, navigation_id):
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = navigation_id
        self._albumsongs = None
        self._context_widget = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect("child-activated", self._on_album_activated)
        self._albumbox.set_max_children_per_line(100)
        self._albumbox.show()

        self._viewport.set_property("valign", Gtk.Align.START)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow)
        self._paned.pack2(self._context, True, False)
        height = Objects.settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Objects.window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
 def __init__(self):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect('destroy', self._on_destroy)
     self._current = (Type.NONE, Type.NONE)
Exemple #6
0
    def __init__(self, album_id):
        Gtk.Popover.__init__(self)
        self._album_id = album_id

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/PopImages.ui')

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect("child-activated", self._on_activate)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._spinner)
        self.add(self._stack)
 def __init__(self):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect('destroy', self.__on_destroy)
     self.__track = Track()
Exemple #8
0
    def __init__(self):
        """
            Init container
        """
        self._pulse_timeout = None
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp().settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_ids, list_two_ids) = self._get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self._list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self._list_two.select_ids(list_two_ids)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Lp().playlists.connect('playlists-changed', self._update_playlists)
 def __init__(self):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect("destroy", self.__on_destroy)
     self.__track = Track()
Exemple #10
0
    def __init__(self):
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_artist = None
        self.add(self._stack)

        Objects.player.connect("current-changed", self._update_content)
Exemple #11
0
    def __init__(self):
        """
            Init popover
        """
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_id = None
        self.add(self._stack)

        Lp.player.connect("current-changed", self._update_content)
Exemple #12
0
    def __init__(self, name, radios_manager):
        """
            Init Popover
            @param name as string
            @param radios_manager as RadiosManager
        """
        Gtk.Popover.__init__(self)
        self._name = name
        self._radios_manager = radios_manager
        self._start = 0
        self._orig_pixbufs = {}

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui')
        builder.connect_signals(self)

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect('child-activated', self._on_activate)
        self._view.set_max_children_per_line(100)
        self._view.set_property('row-spacing', 10)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._logo = builder.get_object('logo')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._name_entry = builder.get_object('name')
        self._uri_entry = builder.get_object('uri')
        self._btn_add_modify = builder.get_object('btn_add_modify')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._logo)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._widget)
        self.add(self._stack)

        if self._name == '':
            builder.get_object('btn_add_modify').set_label(_("Add"))
        else:
            builder.get_object('btn_add_modify').set_label(_("Modify"))
            builder.get_object('btn_delete').show()
            self._name_entry.set_text(self._name)
            uris = self._radios_manager.get_tracks(self._name)
            if len(uris) > 0:
                self._uri_entry.set_text(uris[0])
Exemple #13
0
    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None
        self._lazy_queue = []  # Widgets not initialized
        self._scroll_value = 0
        self._timeout_id = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.get_vadjustment().connect('value-changed',
                                                 self._on_value_changed)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp().settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp().window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
Exemple #14
0
 def __init__(self, artist_id=None):
     """
         Init popover
     """
     ViewContainer.__init__(self, 1000)
     self.connect('destroy', self._on_destroy)
     self._artist_id = artist_id
     self._on_screen_id = None
     self._signal1_id = Lp.player.connect("current-changed",
                                          self._update_content)
     if self._artist_id is None:
         self._signal2_id = Lp.player.connect("current-changed",
                                              self._update_content)
     else:
         self._signal2_id = None
Exemple #15
0
    def __init__(self):
        
        # Try to update db on start, will be done after list one poplating
        # finished
        self._need_to_update_db = True
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Navigation.DEVICES
        self._show_genres = Objects.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        self._list_one_restore = Navigation.POPULARS
        self._list_two_restore = Navigation.NONE
        if Objects.settings.get_value('save-state'):
            self._restore_view_state()

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Objects.playlists.connect("playlists-changed",
                                  self.update_lists)
Exemple #16
0
    def __init__(self, navigation_id):
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = navigation_id
        self._albumsongs = None
        self._context_widget = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect("child-activated", self._on_album_activated)
        self._albumbox.set_max_children_per_line(100)
        self._albumbox.show()

        self._viewport.set_property("valign", Gtk.Align.START)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow)
        self._paned.pack2(self._context, True, False)
        height = Objects.settings.get_value(
                                         'paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Objects.window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
Exemple #17
0
    def __init__(self):
        """
            Init container
        """
        self._pulse_timeout = None
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp().settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_ids, list_two_ids) = self._get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self._list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self._list_two.select_ids(list_two_ids)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)
        for mount in self._vm.get_mounts():
            self._add_device(mount, False)

        Lp().playlists.connect('playlists-changed',
                               self._update_playlists)
Exemple #18
0
    def __init__(self):
        """
            Init container
        """
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_id, list_two_id) = self._get_saved_view_state()
        self._list_one.select_id(list_one_id)
        self._list_two.select_id(list_two_id)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Lp.playlists.connect('playlists-changed',
                             self._update_lists)
Exemple #19
0
    def __init__(self, album_id):
        Gtk.Popover.__init__(self)
        self._album_id = album_id

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource(
                    '/org/gnome/Lollypop/PopImages.ui')

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect("child-activated", self._on_activate)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._spinner)
        self.add(self._stack)
Exemple #20
0
    def __init__(self):

        # Try to update db on start, will be done after list one populating
        # finished
        self._need_to_update_db = Lp.settings.get_value('auto-update') or\
            Lp.tracks.is_empty()
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_id, list_two_id) = self._get_saved_view_state()
        self._list_one.select_id(list_one_id)
        self._list_two.select_id(list_two_id)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Lp.playlists.connect('playlists-changed',
                             self._update_lists)
Exemple #21
0
    def __init__(self):
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_id = None
        self.add(self._stack)

        Lp.player.connect("current-changed", self._update_content)
Exemple #22
0
    def __init__(self, name, radios_manager):
        """
            Init Popover
            @param name as string
            @param radios_manager as RadiosManager
        """
        Gtk.Popover.__init__(self)
        self.connect('map', self._on_map)
        self.connect('unmap', self._on_unmap)
        self._name = name
        self._radios_manager = radios_manager
        self._start = 0
        self._orig_pixbufs = {}

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui')
        builder.connect_signals(self)

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect('child-activated', self._on_activate)
        self._view.set_max_children_per_line(100)
        self._view.set_property('row-spacing', 10)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._logo = builder.get_object('logo')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._name_entry = builder.get_object('name')
        self._uri_entry = builder.get_object('uri')
        self._btn_add_modify = builder.get_object('btn_add_modify')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._logo)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._widget)
        self.add(self._stack)

        if self._name == '':
            builder.get_object('btn_add_modify').set_label(_("Add"))
        else:
            builder.get_object('btn_add_modify').set_label(_("Modify"))
            builder.get_object('btn_delete').show()
            self._name_entry.set_text(self._name)
            uris = self._radios_manager.get_tracks(self._name)
            if len(uris) > 0:
                self._uri_entry.set_text(uris[0])
Exemple #23
0
    def __init__(self, genre_ids, artist_ids):
        """
            Init album view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        LazyLoadingView.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_ids = genre_ids
        self._artist_ids = artist_ids
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.set_property('height-request', 10)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
Exemple #24
0
    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None
        self._lazy_queue = []  # Widgets not initialized
        self._scroll_value = 0
        self._timeout_id = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.get_vadjustment().connect('value-changed',
                                                 self._on_value_changed)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp().settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp().window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
Exemple #25
0
    def __init__(self, genre_ids, artist_ids):
        """
            Init album view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        LazyLoadingView.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_ids = genre_ids
        self._artist_ids = artist_ids
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.set_property('height-request', 10)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)
Exemple #26
0
class Container:
    def __init__(self):

        # Try to update db on start, will be done after list one populating
        # finished
        self._need_to_update_db = Lp.settings.get_value('auto-update') or\
            Lp.tracks.is_empty()
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_id, list_two_id) = self._get_saved_view_state()
        self._list_one.select_id(list_one_id)
        self._list_two.select_id(list_two_id)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Lp.playlists.connect('playlists-changed',
                             self._update_lists)

    """
        Update db at startup only if needed
    """
    def update_db(self):
        # Stop previous scan
        if Lp.scanner.is_locked():
            Lp.scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            # Something (device manager) is using progress bar
            progress = None
            if not self._progress.is_visible():
                progress = self._progress
            Lp.scanner.update(progress)

    """
        Return current selected genre
        @return genre id as int
    """
    def get_genre_id(self):
        if self._show_genres:
            return self._list_one.get_selected_id()
        else:
            return None
    """
        Init list one
    """
    def init_list_one(self):
        self._update_list_one(None)

    """
        Save view state
    """
    def save_view_state(self):
        Lp.settings.set_value("list-one",
                              GLib.Variant('i',
                                           self._list_one.get_selected_id()))
        Lp.settings.set_value("list-two",
                              GLib.Variant('i',
                                           self._list_two.get_selected_id()))

    """
        Show playlist manager for object_id
        Current view stay present in ViewContainer
        @param object id as int
        @param genre id as int
        @param is_album as bool
    """
    def show_playlist_manager(self, object_id, genre_id, is_album):
        view = PlaylistsManageView(object_id, genre_id, is_album,
                                   self._stack.get_allocated_width()/2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Show playlist editor for playlist
        Current view stay present in ViewContainer
        @param playlist name as str
    """
    def show_playlist_editor(self, playlist_name):
        view = PlaylistEditView(playlist_name,
                                self._stack.get_allocated_width()/2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Get main widget
        @return Gtk.HPaned
    """
    def main_widget(self):
        return self._paned_main_list

    """
        Stop current view from processing
    """
    def stop_all(self):
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    """
        Show/Hide genres
        @param bool
    """
    def show_genres(self, show):
        self._show_genres = show
        self._list_one.clear()
        self._update_list_one(None)

    """
        Destroy current view
    """
    def destroy_current_view(self):
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    """
        Update current view
    """
    def update_view(self):
        view = self._stack.get_visible_child()
        if view:
            start_new_thread(view.update_covers, ())

    """
        Mark force scan as False, update lists
        @param scanner as CollectionScanner
    """
    def on_scan_finished(self, scanner):
        if self._list_one.is_populating() or self._list_two.is_populating():
            GLib.timeout_add(500, self.on_scan_finished, scanner)
        else:
            self._update_lists(scanner)
############
# Private  #
############
    """
        Setup window main view:
            - genre list
            - artist list
            - main view as artist view or album view
    """
    def _setup_view(self):
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList()
        self._list_one.show()
        self._list_two = SelectionList()
        self._list_one.connect('item-selected', self._on_list_one_selected)
        self._list_one.connect('populated', self._on_list_populated)
        self._list_two.connect('item-selected', self._on_list_two_selected)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property('hexpand', True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        separator = Gtk.Separator()
        separator.show()
        self._paned_list_view.add1(self._list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Lp.settings.get_value('paned-mainlist-width').get_int32())
        self._paned_list_view.set_position(
            Lp.settings.get_value('paned-listview-width').get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    """
        Get save view state
        @return (list one id, list two id)
    """
    def _get_saved_view_state(self):
        list_one_id = Type.POPULARS
        list_two_id = Type.NONE
        if Lp.settings.get_value('save-state'):
            position = Lp.settings.get_value('list-one').get_int32()
            if position != -1:
                list_one_id = position
            position = Lp.settings.get_value('list-two').get_int32()
            if position != -1:
                list_two_id = position

        return (list_one_id, list_two_id)

    """
        Add genre to genre list
        @param scanner as CollectionScanner
        @param genre id as int
    """
    def _add_genre(self, scanner, genre_id):
        if self._show_genres:
            genre_name = Lp.genres.get_name(genre_id)
            self._list_one.add_value((genre_id, genre_name))

    """
        Add artist to artist list
        @param scanner as CollectionScanner
        @param artist id as int
        @param album id as int
    """
    def _add_artist(self, scanner, artist_id, album_id):
        artist_name = Lp.artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Lp.albums.get_genre_ids(album_id)
            genre_ids.append(Type.ALL)
            if self._list_one.get_selected_id() in genre_ids:
                self._list_two.add_value((artist_id, artist_name))
        else:
            self._list_one.add_value((artist_id, artist_name))

    """
        Run collection update if needed
        @return True if hard scan is running
    """
    def _setup_scanner(self):
        Lp.scanner.connect('scan-finished', self.on_scan_finished)
        Lp.scanner.connect('genre-update', self._add_genre)
        Lp.scanner.connect('artist-update', self._add_artist)

    """
        Update lists
        @param updater as GObject
    """
    def _update_lists(self, updater=None):
        self._update_list_one(updater)
        self._update_list_two(updater)

    """
        Update list one
        @param updater as GObject
    """
    def _update_list_one(self, updater):
        update = updater is not None
        # Do not update if updater is PlaylistsManager
        if not isinstance(updater, PlaylistsManager):
            if self._show_genres:
                self._setup_list_genres(self._list_one, update)
            else:
                self._setup_list_artists(self._list_one,
                                         Type.ALL,
                                         update)

    """
        Update list two
        @param updater as GObject
    """
    def _update_list_two(self, updater):
        update = updater is not None
        object_id = self._list_one.get_selected_id()
        if object_id == Type.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (update,))
        elif self._show_genres and object_id != Type.NONE:
            self._setup_list_artists(self._list_two, object_id, update)

    """
        Return list one headers
    """
    def _get_headers(self):
        items = []
        items.append((Type.POPULARS, _("Popular albums")))
        items.append((Type.RECENTS, _("Recent albums")))
        items.append((Type.RANDOMS, _("Random albums")))
        items.append((Type.PLAYLISTS, _("Playlists")))
        items.append((Type.RADIOS, _("Radios")))
        if self._show_genres:
            items.append((Type.ALL, _("All artists")))
        else:
            items.append((Type.ALL, _("All albums")))
        return items

    """
        Setup list for genres
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """
    def _setup_list_genres(self, selection_list, update):
        sql = Lp.db.get_cursor()
        selection_list.mark_as_artists(False)
        items = self._get_headers()
        items.append((Type.SEPARATOR, ''))
        items += Lp.genres.get(sql)
        if update:
            selection_list.update_values(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Hide list two base on current artist list
    """
    def _pre_setup_list_artists(self, selection_list):
        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.hide()
            self._list_two_restore = Type.NONE

    """
        Setup list for artists
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """
    def _setup_list_artists(self, selection_list, genre_id, update):
        GLib.idle_add(self._pre_setup_list_artists, selection_list)
        sql = Lp.db.get_cursor()
        items = []
        selection_list.mark_as_artists(True)
        if selection_list == self._list_one:
            items = self._get_headers()
        if Lp.albums.get_compilations(genre_id, sql):
            items.append((Type.COMPILATIONS, _("Compilations")))
        items.append((Type.SEPARATOR, ''))
        items += Lp.artists.get(genre_id, sql)

        if update:
            selection_list.update_values(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Setup list for playlists
        @param update as bool
        @thread safe
    """
    def _setup_list_playlists(self, update):
        playlists = Lp.playlists.get()
        if update:
            self._list_two.update_values(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)

    """
        Update current view with device view,
        Use existing view if available
        @param device id as int
    """
    def _update_view_device(self, device_id):
        device = self._devices[device_id]

        if device and device.view:
            view = device.view
        else:
            view = DeviceView(device, self._progress,
                              self._stack.get_allocated_width()/2)
            device.view = view
            view.show()
            start_new_thread(view.populate, ())
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with artists view
        @param artist id as int
        @param genre id as int
    """
    def _update_view_artists(self, artist_id, genre_id):
        view = ArtistView(artist_id, genre_id)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, ())
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with albums view
        @param genre id as int
        @param is compilation as bool
    """
    def _update_view_albums(self, genre_id, is_compilation=False):
        view = AlbumsView(genre_id, is_compilation)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, ())
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with playlist view
        @param playlist id as int
    """
    def _update_view_playlists(self, playlist_id):
        view = None
        if playlist_id is not None:
            for (p_id, p_str) in Lp.playlists.get():
                if p_id == playlist_id:
                    view = PlaylistsView(p_str, self._stack)
                    break
        else:
            view = PlaylistsManageView(-1, None, False,
                                       self._stack.get_allocated_width()/2)
        if view:
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            start_new_thread(view.populate, ())
            self._stack.clean_old_views(view)

    """
        Update current view with radios view
    """
    def _update_view_radios(self):
        view = RadiosView()
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, ())
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Add volume to device list
        @param volume as Gio.Volume
    """
    def _add_device(self, volume):
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        # Just to be sure
        if uri is not None and len(uri) > 1 and uri[-1:] != '/':
            uri += '/'
        if uri is not None and uri.find('mtp:') != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = volume.get_name()
            dev.uri = uri
            self._devices[self._devices_index] = dev
            if not self._list_one.is_populating():
                self._list_one.add_value((dev.id, dev.name))

    """
        Remove volume from device list
        @param volume as Gio.Volume
    """
    def _remove_device(self, volume):
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        for dev in self._devices.values():
            if dev.uri == uri:
                self._list_one.remove(dev.id)
                device = self._devices[dev.id]
                if device.view:
                    device.view.destroy()
                del self._devices[dev.id]
            break

    """
        Update view based on selected object
        @param list as SelectionList
        @param selected id as int
    """
    def _on_list_one_selected(self, selection_list, selected_id):
        if selected_id == Type.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (False,))
            self._list_two.clear()
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_playlists(None)
        elif selected_id < Type.DEVICES:
            self._list_two.hide()
            if not self._list_two.will_be_selected():
                self._update_view_device(selected_id)
        elif selected_id in [Type.POPULARS,
                             Type.RECENTS,
                             Type.RANDOMS]:
            self._list_two.hide()
            self._update_view_albums(selected_id)
        elif selected_id == Type.RADIOS:
            self._list_two.hide()
            self._update_view_radios()
        elif selection_list.is_marked_as_artists():
            self._list_two.hide()
            if selected_id == Type.ALL:
                self._update_view_albums(selected_id)
            elif selected_id == Type.COMPILATIONS:
                self._update_view_albums(None, True)
            else:
                self._update_view_artists(selected_id, None)
        else:
            self._list_two.clear()
            start_new_thread(self._setup_list_artists,
                             (self._list_two, selected_id, False))
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_albums(selected_id, False)

    """
        Add device to list one and update db
        @param selection list as SelectionList
    """
    def _on_list_populated(self, selection_list):
        if self._list_one.is_populating() or\
           self._list_two.is_populating():
            GLib.timeout_add(500, self._on_list_populated, selection_list)
            return

        for dev in self._devices.values():
            self._list_one.add_value((dev.id, dev.name))
        if self._need_to_update_db:
            self._need_to_update_db = False
            self.update_db()

    """
        Update view based on selected object
        @param list as SelectionList
        @param selected id as int
    """
    def _on_list_two_selected(self, selection_list, selected_id):
        genre_id = self._list_one.get_selected_id()
        if genre_id == Type.PLAYLISTS:
            self._update_view_playlists(selected_id)
        elif selected_id == Type.COMPILATIONS:
            self._update_view_albums(genre_id, True)
        else:
            self._update_view_artists(selected_id, genre_id)

    """
        On volume mounter
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """
    def _on_mount_added(self, vm, mnt):
        self._add_device(mnt.get_volume())

    """
        On volume removed, clean selection list
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """
    def _on_mount_removed(self, vm, mnt):
        self._remove_device(mnt.get_volume())
Exemple #27
0
class PopImages(Gtk.Popover):
    """
        Init Popover ui with a text entry and a scrolled treeview
    """
    def __init__(self, album_id):
        Gtk.Popover.__init__(self)
        self._album_id = album_id

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/PopImages.ui')

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect("child-activated", self._on_activate)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._spinner)
        self.add(self._stack)

    """
        Populate view
        @param searched words as string
    """

    def populate(self, string):
        self._thread = True
        start_new_thread(self._populate, (string, ))

    """
        Resize popover and set signals callback
    """

    def do_show(self):
        self.set_size_request(700, 400)
        Gtk.Popover.do_show(self)

    """
        Kill thread
    """

    def do_hide(self):
        self._thread = False
        Gtk.Popover.do_hide(self)

#######################
# PRIVATE             #
#######################

    """
        Same as populate()
    """
    def _populate(self, string):
        self._urls = Objects.art.get_google_arts(string)
        if self._urls:
            self._add_pixbufs()
        else:
            GLib.idle_add(self._show_not_found)

    """
        Add urls to the view
    """

    def _add_pixbufs(self):
        if self._urls:
            url = self._urls.pop()
            stream = None
            try:
                response = urllib.request.urlopen(url)
                stream = Gio.MemoryInputStream.new_from_data(
                    response.read(), None)
            except:
                if self._thread:
                    self._add_pixbufs()
            if stream:
                GLib.idle_add(self._add_pixbuf, stream)
            if self._thread:
                self._add_pixbufs()

    """
        Show not found message
    """

    def _show_not_found(self):
        self._stack.set_visible_child(self._not_found)
        self._stack.clean_old_views(self._not_found)

    """
        Add stream to the view
    """

    def _add_pixbuf(self, stream):
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
                stream, ArtSize.MONSTER, ArtSize.MONSTER, False, None)
            image = Gtk.Image()
            image.set_from_pixbuf(
                pixbuf.scale_simple(ArtSize.BIG, ArtSize.BIG, 2))
            image.show()
            self._view.add(image)
        except Exception as e:
            print(e)
            pass
        # Remove spinner if exist
        if self._spinner is not None:
            self._stack.set_visible_child(self._widget)
            self._stack.clean_old_views(self._widget)
            self._spinner = None

    """
        Use pixbuf as cover
        Reset cache and use player object to announce cover change
    """

    def _on_activate(self, flowbox, child):
        pixbuf = child.get_child().get_pixbuf()
        Objects.art.save_art(pixbuf, self._album_id)
        Objects.art.clean_cache(self._album_id)
        Objects.player.announce_cover_update(self._album_id)
        self.hide()
        self._streams = {}
Exemple #28
0
class Container:
    """
        Container for main view
    """
    def __init__(self):
        """
            Init container
        """
        self.__pulse_timeout = None
        # Index will start at -VOLUMES
        self.__devices = {}
        self.__devices_index = Type.DEVICES
        self.__show_genres = Lp().settings.get_value('show-genres')
        self.__stack = ViewContainer(500)
        self.__stack.show()

        self.__setup_view()
        self.__setup_scanner()

        (list_one_ids, list_two_ids) = self.__get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self.__list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self.__list_two.select_ids(list_two_ids)

        # Volume manager
        self.__vm = Gio.VolumeMonitor.get()
        self.__vm.connect('mount-added', self.__on_mount_added)
        self.__vm.connect('mount-removed', self.__on_mount_removed)
        for mount in self.__vm.get_mounts():
            self.__add_device(mount, False)

        Lp().playlists.connect('playlists-changed',
                               self.__update_playlists)

    def update_db(self):
        """
            Update db at startup only if needed
        """
        # Stop previous scan
        if Lp().scanner.is_locked():
            Lp().scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            Lp().scanner.update()

    def get_genre_id(self):
        """
            Return current selected genre
            @return genre id as int
        """
        if self.__show_genres:
            return self.__list_one.get_selected_id()
        else:
            return None

    def init_list_one(self):
        """
            Init list one
        """
        self.__update_list_one(None)

    def save_view_state(self):
        """
            Save view state
        """
        Lp().settings.set_value(
                            "list-one-ids",
                            GLib.Variant('ai',
                                         self.__list_one.selected_ids))
        Lp().settings.set_value(
                            "list-two-ids",
                            GLib.Variant('ai',
                                         self.__list_two.selected_ids))

    def show_playlist_manager(self, object_id, genre_ids,
                              artist_ids, is_album):
        """
            Show playlist manager for object_id
            Current view stay present in ViewContainer
            @param object id as int
            @param genre ids as [int]
            @param artist ids as [int]
            @param is_album as bool
        """
        from lollypop.view_playlists import PlaylistsManageView
        current = self.__stack.get_visible_child()
        view = PlaylistsManageView(object_id, genre_ids, artist_ids, is_album)
        view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        current.disable_overlay()

    def show_playlist_editor(self, playlist_id):
        """
            Show playlist editor for playlist
            Current view stay present in ViewContainer
            @param playlist id as int
            @param playlist name as str
        """
        from lollypop.view_playlists import PlaylistEditView
        view = PlaylistEditView(playlist_id)
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)
        view.populate()

    def get_view_width(self):
        """
            Return view width
            @return width as int
        """
        return self.__stack.get_allocation().width

    def stop_all(self):
        """
            Stop current view from processing
        """
        view = self.__stack.get_visible_child()
        if view is not None:
            self.__stack.clean_old_views(None)

    def show_genres(self, show):
        """
            Show/Hide genres
            @param bool
        """
        self.__show_genres = show
        self.__list_one.clear()
        self.__list_two.clear()
        self.__list_two.hide()
        self.__update_list_one(None)
        self.__list_one.select_ids([Type.POPULARS])

    def destroy_current_view(self):
        """
            Destroy current view
        """
        view = self.__stack.get_visible_child()
        for child in self.__stack.get_children():
            if child != view:
                self.__stack.set_visible_child(child)
                self.__stack.clean_old_views(child)
                break

    @property
    def view(self):
        """
            Disable overlays
        """
        return self.__stack.get_visible_child()

    @property
    def progress(self):
        """
            Progress bar
            @return ProgressBar
        """
        return self.__progress

    def add_remove_from(self, value, list_one, add):
        """
            Add or remove value to list
            @param value as (int, str)
            @param list one as bool
            @param add as bool
        """
        if list_one:
            l = self.__list_one
        else:
            l = self.__list_two
        if add:
            l.add_value(value)
        else:
            l.remove_value(value[0])

    def reload_view(self):
        """
            Reload current view
        """
        values_two = self.__list_two.selected_ids
        values_one = self.__list_one.selected_ids
        if not values_one:
            values_one = [Type.POPULARS]
        self.__list_one.select_ids([])
        self.__list_one.clear()
        self.__update_list_one(None)
        self.__list_one.select_ids(values_one)
        if self.__list_two.is_visible():
            self.__list_two.select_ids([])
            self.__list_two.clear()
            self.__update_list_two(None)
            self.__list_two.select_ids(values_two)

    def pulse(self, pulse):
        """
            Make progress bar visible/pulse if pulse is True
            @param pulse as bool
        """
        if pulse and not self.__progress.is_visible():
            self.__progress.show()
            if self.__pulse_timeout is None:
                self.__pulse_timeout = GLib.timeout_add(500, self.__pulse)
        else:
            if self.__pulse_timeout is not None:
                GLib.source_remove(self.__pulse_timeout)
                self.__pulse_timeout = None
                self.__progress.hide()

    def on_scan_finished(self, scanner):
        """
            Mark force scan as False, update lists
            @param scanner as CollectionScanner
        """
        self.__update_lists(scanner)

    def add_fake_phone(self):
        """
            Emulate an Android Phone
        """
        self.__devices_index -= 1
        dev = Device()
        dev.id = self.__devices_index
        dev.name = "Android phone"
        dev.uri = "file:///tmp/android/"
        d = Lio.File.new_for_uri(dev.uri+"Internal Memory")
        if not d.query_exists():
            d.make_directory_with_parents()
        d = Lio.File.new_for_uri(dev.uri+"SD Card")
        if not d.query_exists():
            d.make_directory_with_parents()
        self.__devices[self.__devices_index] = dev

    def show_artists_albums(self, artist_ids):
        """
            Show albums from artists
        """
        self.__update_view_artists([], artist_ids)
        GLib.idle_add(self.__list_two.hide)
        GLib.idle_add(self.__list_one.select_ids, [])

##############
# PROTECTED  #
##############
    def _hide_pane(self):
        """
            Hide navigation pane
            Internally hide list one and list two
        """
        if self.__list_one.is_visible():
            self.__list_two.hide()
            self.__list_one.hide()
        else:
            self.__list_one.show()
            if self.__list_two.was_visible:
                self.__list_two.show()
        Lp().settings.set_value('show-navigation-list',
                                GLib.Variant('b',
                                             self.__list_one.is_visible()))

############
# PRIVATE  #
############
    def __pulse(self):
        """
            Make progress bar pulse while visible
            @param pulse as bool
        """
        if self.__progress.is_visible() and not Lp().scanner.is_locked():
            self.__progress.pulse()
            return True
        else:
            self.__progress.set_fraction(0.0, self)
            return False

    def __setup_view(self):
        """
            Setup window main view:
                - genre list
                - artist list
                - main view as artist view or album view
        """
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self.__list_one = SelectionList()
        if Lp().settings.get_value('show-navigation-list'):
            self.__list_one.show()
        self.__list_two = SelectionList()
        self.__list_one.connect('item-selected', self.__on_list_one_selected)
        self.__list_one.connect('populated', self.__on_list_populated)
        self.__list_two.connect('item-selected', self.__on_list_two_selected)

        self.__progress = ProgressBar()
        self.__progress.set_property('hexpand', True)

        vgrid.add(self.__stack)
        vgrid.add(self.__progress)
        vgrid.show()

        self._paned_list_view.add1(self.__list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self.__list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Lp().settings.get_value('paned-mainlist-width').get_int32())
        self._paned_list_view.set_position(
            Lp().settings.get_value('paned-listview-width').get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    def __get_saved_view_state(self):
        """
            Get save view state
            @return (list one id, list two id)
        """
        list_one_ids = [Type.POPULARS]
        list_two_ids = [Type.NONE]
        if Lp().settings.get_value('save-state'):
            list_one_ids = []
            list_two_ids = []
            ids = Lp().settings.get_value('list-one-ids')
            for i in ids:
                if isinstance(i, int):
                    list_one_ids.append(i)
            ids = Lp().settings.get_value('list-two-ids')
            for i in ids:
                if isinstance(i, int):
                    list_two_ids.append(i)
        return (list_one_ids, list_two_ids)

    def __setup_scanner(self):
        """
            Run collection update if needed
            @return True if hard scan is running
        """
        Lp().scanner.connect('scan-finished', self.on_scan_finished)
        Lp().scanner.connect('genre-updated', self.__on_genre_updated)
        Lp().scanner.connect('artist-updated', self.__on_artist_updated)

    def __update_playlists(self, playlists, playlist_id):
        """
            Update playlists in second list
            @param playlists as Playlists
            @param playlist_id as int
        """
        ids = self.__list_one.selected_ids
        if ids and ids[0] == Type.PLAYLISTS:
            if Lp().playlists.exists(playlist_id):
                self.__list_two.update_value(playlist_id,
                                             Lp().playlists.get_name(
                                                                  playlist_id))
            else:
                self.__list_two.remove_value(playlist_id)

    def __update_lists(self, updater=None):
        """
            Update lists
            @param updater as GObject
        """
        self.__update_list_one(updater)
        self.__update_list_two(updater)

    def __update_list_one(self, updater):
        """
            Update list one
            @param updater as GObject
        """
        update = updater is not None
        if self.__show_genres:
            self.__update_list_genres(self.__list_one, update)
        else:
            self.__update_list_artists(self.__list_one, [Type.ALL], update)

    def __update_list_two(self, updater):
        """
            Update list two
            @param updater as GObject
        """
        update = updater is not None
        ids = self.__list_one.selected_ids
        if ids and ids[0] == Type.PLAYLISTS:
            self.__update_list_playlists(update)
        elif self.__show_genres and ids:
            self.__update_list_artists(self.__list_two, ids, update)

    def __update_list_genres(self, selection_list, update):
        """
            Setup list for genres
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            genres = Lp().genres.get()
            return genres

        def setup(genres):
            items = selection_list.get_headers()
            items.append((Type.SEPARATOR, ''))
            items += genres
            selection_list.mark_as_artists(False)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        loader = Loader(target=load, view=selection_list, on_finished=setup)
        loader.start()

    def __update_list_artists(self, selection_list, genre_ids, update):
        """
            Setup list for artists
            @param list as SelectionList
            @param genre ids as [int]
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            artists = Lp().artists.get(genre_ids)
            compilations = Lp().albums.get_compilation_ids(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            if selection_list == self.__list_one:
                items = selection_list.get_headers()
                if not compilations:
                    items.append((Type.SEPARATOR, ''))
            else:
                items = []
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
                items.append((Type.SEPARATOR, ''))
            items += artists
            selection_list.mark_as_artists(True)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        if selection_list == self.__list_one:
            if self.__list_two.is_visible():
                self.__list_two.hide()
            self.__list_two_restore = Type.NONE
        loader = Loader(target=load, view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def __update_list_charts(self):
        """
            Setup list for charts
            @thread safe
        """
        def load():
            genres = Lp().genres.get_charts()
            return genres

        def setup(genres):
            genres.insert(0, (Type.SEPARATOR, ''))
            genres.insert(0, (Type.ITUNES, 'Itunes'))
            genres.insert(0, (Type.LASTFM, 'Last.fm'))
            genres.insert(0, (Type.SPOTIFY, "Spotify"))
            self.__list_two.populate(genres)
            self.__list_two.mark_as_artists(False)
        loader = Loader(target=load, view=self.__list_two, on_finished=setup)
        loader.start()

    def __update_list_playlists(self, update):
        """
            Setup list for playlists
            @param update as bool
            @thread safe
        """
        items = self.__list_two.get_pl_headers()
        items += Lp().playlists.get()
        if update:
            self.__list_two.update_values(items)
        else:
            self.__list_two.mark_as_artists(False)
            self.__list_two.populate(items)

    def __stop_current_view(self):
        """
            Stop current view
        """
        child = self.__stack.get_visible_child()
        if child is not None:
            if hasattr(child, "stop"):
                child.stop()

    def __update_view_device(self, device_id):
        """
            Update current view with device view,
            Use existing view if available
            @param device id as int
        """
        from lollypop.view_device import DeviceView, DeviceLocked
        self.__stop_current_view()
        device = self.__devices[device_id]
        child = self.__stack.get_child_by_name(device.uri)
        if child is None:
            files = DeviceView.get_files(device.uri)
            if files:
                if child is None:
                    child = DeviceView(device)
                    self.__stack.add_named(child, device.uri)
            else:
                child = DeviceLocked()
                self.__stack.add(child)
            child.show()
        child.populate()
        self.__stack.set_visible_child(child)
        self.__stack.clean_old_views(child)

    def __update_view_artists(self, genre_ids, artist_ids):
        """
            Update current view with artists view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        def load():
            if genre_ids and genre_ids[0] == Type.ALL:
                albums = Lp().albums.get_ids(artist_ids, [])
            else:
                albums = []
                if artist_ids and artist_ids[0] == Type.COMPILATIONS:
                    albums += Lp().albums.get_compilation_ids(genre_ids)
                albums += Lp().albums.get_ids(artist_ids, genre_ids)
            return albums
        from lollypop.view_artist import ArtistView
        self.__stop_current_view()
        view = ArtistView(artist_ids, genre_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_albums(self, genre_ids, artist_ids):
        """
            Update current view with albums view
            @param genre ids as [int]
            @param is compilation as bool
        """
        def load():
            items = []
            is_compilation = artist_ids and artist_ids[0] == Type.COMPILATIONS
            if genre_ids and genre_ids[0] == Type.ALL:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    items = Lp().albums.get_compilation_ids()
                if not is_compilation:
                    items += Lp().albums.get_ids()
            elif genre_ids and genre_ids[0] == Type.POPULARS:
                items = Lp().albums.get_rated()
                count = 100 - len(items)
                for album in Lp().albums.get_populars(count):
                    if album not in items:
                        items.append(album)
            elif genre_ids and genre_ids[0] == Type.LOVED:
                items = Lp().albums.get_loves()
            elif genre_ids and genre_ids[0] == Type.RECENTS:
                items = Lp().albums.get_recents()
            elif genre_ids and genre_ids[0] == Type.RANDOMS:
                items = Lp().albums.get_randoms()
            elif genre_ids and genre_ids[0] in [Type.SPOTIFY,
                                                Type.LASTFM]:
                items = Lp().tracks.get_charts_ids(genre_ids)
            elif genre_ids and genre_ids[0] == Type.ITUNES:
                items = Lp().albums.get_charts_ids(genre_ids)
            elif artist_ids and artist_ids[0] == Type.CHARTS:
                items = Lp().albums.get_charts_ids(genre_ids)
            else:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    items = Lp().albums.get_compilation_ids(genre_ids)
                if not is_compilation:
                    items += Lp().albums.get_ids([], genre_ids)
            return items

        # Spotify albums contains only one tracks, show playlist view
        if genre_ids and genre_ids[0] in [Type.SPOTIFY,
                                          Type.LASTFM]:
            from lollypop.view_playlists import PlaylistsView
            view = PlaylistsView(genre_ids)
            loader = Loader(target=load, view=view)
            loader.start()
        else:
            from lollypop.view_albums import AlbumsView
            self.__stop_current_view()
            view = AlbumsView(genre_ids, artist_ids)
            loader = Loader(target=load, view=view)
            loader.start()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_playlists(self, playlist_ids=[]):
        """
            Update current view with playlist view
            @param playlist ids as [int]
        """
        def load():
            track_ids = []
            for playlist_id in playlist_ids:
                if playlist_id == Type.POPULARS:
                    tracks = Lp().tracks.get_rated()
                    for track in Lp().tracks.get_populars():
                        tracks.append(track)
                elif playlist_id == Type.RECENTS:
                    tracks = Lp().tracks.get_recently_listened_to()
                elif playlist_id == Type.NEVER:
                    tracks = Lp().tracks.get_never_listened_to()
                elif playlist_id == Type.RANDOMS:
                    tracks = Lp().tracks.get_randoms()
                elif playlist_id == Type.LOVED:
                    tracks = Lp().playlists.get_track_ids_sorted(playlist_id)
                else:
                    tracks = Lp().playlists.get_track_ids(playlist_id)
                for track_id in tracks:
                    if track_id not in track_ids:
                        track_ids.append(track_id)
            return track_ids

        self.__stop_current_view()
        view = None
        if playlist_ids:
            from lollypop.view_playlists import PlaylistsView
            view = PlaylistsView(playlist_ids)
            loader = Loader(target=load, view=view)
            loader.start()
        else:
            from lollypop.view_playlists import PlaylistsManageView
            view = PlaylistsManageView(Type.NONE, [], [], False)
            view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_radios(self):
        """
            Update current view with radios view
        """
        from lollypop.view_radios import RadiosView
        self.__stop_current_view()
        view = RadiosView()
        view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __add_device(self, mount, show=False):
        """
            Add a device
            @param mount as Gio.Mount
            @param show as bool
        """
        if mount.get_volume() is None:
            return
        name = mount.get_name()
        uri = mount.get_default_location().get_uri()
        if uri is not None and (
                mount.can_eject() or uri.startswith('mtp')):
            self.__devices_index -= 1
            dev = Device()
            dev.id = self.__devices_index
            dev.name = name
            dev.uri = uri
            self.__devices[self.__devices_index] = dev
            if show:
                self.__list_one.add_value((dev.id, dev.name))

    def __remove_device(self, mount):
        """
            Remove volume from device list
            @param mount as Gio.Mount
        """
        uri = mount.get_default_location().get_uri()
        for dev in self.__devices.values():
            if dev.uri == uri:
                self.__list_one.remove_value(dev.id)
                child = self.__stack.get_child_by_name(uri)
                if child is not None:
                    child.destroy()
                del self.__devices[dev.id]
            break

    def __on_list_one_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        selected_ids = self.__list_one.selected_ids
        if not selected_ids:
            return
        self.__list_two.clear()
        if selected_ids[0] == Type.PLAYLISTS:
            if Lp().settings.get_value('show-navigation-list'):
                self.__list_two.show()
            if not self.__list_two.will_be_selected():
                self.__update_view_playlists()
            self.__update_list_playlists(False)
        elif Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES:
            self.__list_two.hide()
            if not self.__list_two.will_be_selected():
                self.__update_view_device(selected_ids[0])
        elif selected_ids[0] == Type.CHARTS:
            self.__list_two.show()
            self.__update_list_charts()
            self.__update_view_albums(selected_ids, [])
        elif selected_ids[0] in [Type.POPULARS,
                                 Type.LOVED,
                                 Type.RECENTS,
                                 Type.RANDOMS]:
            self.__list_two.hide()
            self.__update_view_albums(selected_ids, [])
        elif selected_ids[0] == Type.RADIOS:
            self.__list_two.hide()
            self.__update_view_radios()
        elif selection_list.is_marked_as_artists():
            self.__list_two.hide()
            if selected_ids[0] == Type.ALL:
                self.__update_view_albums(selected_ids, [])
            elif selected_ids[0] == Type.COMPILATIONS:
                self.__update_view_albums([], selected_ids)
            else:
                self.__update_view_artists([], selected_ids)
        else:
            self.__update_list_artists(self.__list_two, selected_ids, False)
            if Lp().settings.get_value('show-navigation-list'):
                self.__list_two.show()
            if not self.__list_two.will_be_selected():
                self.__update_view_albums(selected_ids, [])

    def __on_list_populated(self, selection_list):
        """
            Add device to list one and update db
            @param selection list as SelectionList
        """
        for dev in self.__devices.values():
            self.__list_one.add_value((dev.id, dev.name))

    def __on_list_two_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        genre_ids = self.__list_one.selected_ids
        selected_ids = self.__list_two.selected_ids
        if not selected_ids or not genre_ids:
            return
        if genre_ids[0] == Type.PLAYLISTS:
            self.__update_view_playlists(selected_ids)
        elif genre_ids[0] == Type.CHARTS:
            self.__update_view_albums(selected_ids, [Type.CHARTS])
        elif selected_ids[0] == Type.COMPILATIONS:
            self.__update_view_albums(genre_ids, selected_ids)
        else:
            self.__update_view_artists(genre_ids, selected_ids)

    def __on_genre_updated(self, scanner, genre_id, add):
        """
            Add genre to genre list
            @param scanner as CollectionScanner
            @param genre id as int
            @param add as bool
        """
        # Ignore static genres, can happend with Charts
        if genre_id < 0:
            return
        if self.__show_genres:
            if add:
                genre_name = Lp().genres.get_name(genre_id)
                self.__list_one.add_value((genre_id, genre_name))
            else:
                genre_ids = Lp().genres.get_ids()
                if genre_id not in genre_ids:
                    self.__list_one.remove_value(genre_id)

    def __on_artist_updated(self, scanner, artist_id, add):
        """
            Add artist to artist list
            @param scanner as CollectionScanner
            @param artist id as int
            @param add as bool
        """
        artist_name = Lp().artists.get_name(artist_id)
        sortname = Lp().artists.get_sortname(artist_id)
        if self.__show_genres:
            l = self.__list_two
            artist_ids = Lp().artists.get_ids(self.__list_one.selected_ids)
        else:
            l = self.__list_one
            artist_ids = Lp().artists.get_ids()
        if add:
            if artist_id in artist_ids:
                l.add_value((artist_id, artist_name, sortname))
        else:
            if artist_id not in artist_ids:
                l.remove_value(artist_id)

    def __on_mount_added(self, vm, mount):
        """
            On volume mounter
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self.__add_device(mount, True)

    def __on_mount_removed(self, vm, mount):
        """
            On volume removed, clean selection list
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self.__remove_device(mount)
Exemple #29
0
class AlbumsView(View):
    """
        Show albums in a box
    """

    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None
        self._lazy_queue = []  # Widgets not initialized
        self._scroll_value = 0
        self._timeout_id = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.get_vadjustment().connect('value-changed',
                                                 self._on_value_changed)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp().settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp().window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self, albums):
        """
            Populate albums
            @param is compilation as bool
        """
        # Add first album to get album size,
        # used to precalculate next albums size
        if albums:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.init_widget()
            widget.show_all()
            self._albumbox.insert(widget, -1)
            # Keep album requisition
            self._requisition = widget.get_preferred_size()[1]
            self._add_albums(albums)

#######################
# PRIVATE             #
#######################
    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumContextWidget(album_id,
                                                  self._genre_id,
                                                  True,
                                                  size_group)
        self._context_widget.populate()
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    def _add_albums(self, albums):
        """
            Add albums to the view
            Start lazy loading
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0),
                                       self._requisition.width,
                                       self._requisition.height)
            self._albumbox.insert(widget, -1)
            widget.show_all()
            self._lazy_queue.append(widget)
            GLib.idle_add(self._add_albums, albums)
        else:
            GLib.idle_add(self._lazy_loading)
            self._viewport.add(self._albumbox)

    def _lazy_loading(self, widgets=[], scroll_value=0):
        """
            Load the view in a lazy way:
                - widgets first
                - _waiting_init then
            @param widgets as [AlbumSimpleWidgets]
            @param scroll_value as float
        """
        widget = None
        if self._stop or self._scroll_value != scroll_value:
            return
        if widgets:
            widget = widgets.pop(0)
            self._lazy_queue.remove(widget)
        elif self._lazy_queue:
            widget = self._lazy_queue.pop(0)
        if widget is not None:
            widget.init_widget()
            GLib.idle_add(self._lazy_loading, widgets, scroll_value)

    def _is_visible(self, widget):
        """
            Is widget visible in scrolled
            @param widget as Gtk.Widget
        """
        widget_alloc = widget.get_allocation()
        scrolled_alloc = self._scrolled.get_allocation()
        try:
            (x, y) = widget.translate_coordinates(self._scrolled, 0, 0)
            return (y > -widget_alloc.height-ArtSize.BIG or y >= 0) and\
                y < scrolled_alloc.height+ArtSize.BIG
        except:
            return True

    def _lazy_or_not(self):
        """
            Add visible widgets to lazy queue
        """
        self._timeout_id = None
        widgets = []
        for child in self._lazy_queue:
            if self._is_visible(child):
                widgets.append(child)
        GLib.idle_add(self._lazy_loading, widgets, self._scroll_value)

    def _on_value_changed(self, adj):
        """
            Update scroll value and check for lazy queue
            @param adj as Gtk.Adjustment
        """
        if self._timeout_id is not None:
            GLib.source_remove(self._timeout_id)
            self._timeout_id = None
        self._scroll_value = adj.get_value()
        if not self._lazy_queue:
            return
        self._timeout_id = GLib.timeout_add(250, self._lazy_or_not)

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        Lp().settings.set_value('paned-context-height',
                                GLib.Variant('i', paned.get_position()))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @param child as Gtk.FlowboxChild
        """
        album_widget = child.get_child()
        if self._press_rect is None:
            if self._context_album_id == album_widget.get_id():
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            else:
                if Lp().settings.get_value('auto-play'):
                    album = Album(album_widget.get_id())
                    track = Track(album.tracks_ids[0])
                    Lp().player.load(track)
                    Lp().player.set_albums(track.id, None,
                                           self._genre_id)
                else:
                    self._context_album_id = album_widget.get_id()
                    self._populate_context(self._context_album_id)
                    self._context.show()
        else:
            if self._context_album_id is not None:
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            popover = AlbumPopoverWidget(album_widget.get_id(),
                                         self._genre_id)
            popover.set_relative_to(album_widget)
            popover.set_pointing_to(self._press_rect)
            self._context_widget = popover.get_widget()
            popover.connect('destroy', self._on_popover_destroyed)
            popover.show()

    def _on_popover_destroyed(self, popover):
        """
            Remove from context
            @param popover as AlbumPopoverWidget
        """
        self._context_widget = None

    def _on_button_press(self, flowbox, event):
        """
            Store pressed button
            @param flowbox as Gtk.Flowbox
            @param event as Gdk.EventButton
        """
        if event.button == 1:
            self._press_rect = None
        else:
            self._press_rect = Gdk.Rectangle()
            self._press_rect.x = event.x
            self._press_rect.y = event.y
            self._press_rect.width = self._press_rect.height = 1
            event.button = 1
Exemple #30
0
class AlbumsPopover(Gtk.Popover):
    """
        Popover => CurrentArtistView
    """
    def __init__(self):
        """
            Init popover
        """
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_id = None
        self.add(self._stack)

        Lp.player.connect("current-changed", self._update_content)

    def populate(self):
        """
            Load content of the popover
        """
        if Lp.player.current_track.album_artist_id == Type.COMPILATIONS:
            new_id = Lp.player.current_track.album_id
        else:
            new_id = Lp.player.current_track.album_artist_id
        if self._on_screen_id != new_id:
            self._on_screen_id = new_id
            view = CurrentArtistView(Lp.player.current_track.album_artist_id)
            albums = self._get_albums(Lp.player.current_track.album_artist_id)
            view.populate(albums)
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    def do_show(self):
        """
            Resize popover
        """
        size_setting = Lp.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and\
           isinstance(size_setting[1], int):
            self.set_size_request(size_setting[0] * 0.65,
                                  size_setting[1] * 0.8)
        else:
            self.set_size_request(600, 600)
        Gtk.Popover.do_show(self)

    def do_hide(self):
        """
            Remove view
        """
        Gtk.Popover.do_hide(self)
        child = self._stack.get_visible_child()
        if child is not None:
            child.stop()
        self._on_screen_id = None
        self._stack.clean_old_views(None)

#######################
# PRIVATE             #
#######################

    def _get_albums(self, artist_id):
        """
            Get albums
            @return album ids as [int]
        """
        sql = Lp.db.get_cursor()
        if artist_id == Type.COMPILATIONS:
            albums = [Lp.player.current_track.album_id]
        else:
            albums = Lp.artists.get_albums(artist_id, sql)
        sql.close()
        return albums

    def _update_content(self, player):
        """
            Update the content view
            @param player as Player
            @param track id as int
        """
        if self.is_visible():
            self.populate()

    def do_get_preferred_width(self):
        """
            Set 0 to force popover to not expand
        """
        return (0, 0)
Exemple #31
0
class Container:
    """
        Container for main view
    """

    def __init__(self):
        """
            Init container
        """
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp.settings.get_value("show-genres")
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_id, list_two_id) = self._get_saved_view_state()
        self._list_one.select_id(list_one_id)
        self._list_two.select_id(list_two_id)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect("mount-added", self._on_mount_added)
        self._vm.connect("mount-removed", self._on_mount_removed)

        Lp.playlists.connect("playlists-changed", self._update_lists)

    def update_db(self):
        """
            Update db at startup only if needed
        """
        # Stop previous scan
        if Lp.scanner.is_locked():
            Lp.scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            # Something (device manager) is using progress bar
            progress = None
            if not self._progress.is_visible():
                progress = self._progress
            Lp.scanner.update(progress)

    def get_genre_id(self):
        """
            Return current selected genre
            @return genre id as int
        """
        if self._show_genres:
            return self._list_one.get_selected_id()
        else:
            return None

    def init_list_one(self):
        """
            Init list one
        """
        self._update_list_one(None)

    def save_view_state(self):
        """
            Save view state
        """
        Lp.settings.set_value("list-one", GLib.Variant("i", self._list_one.get_selected_id()))
        Lp.settings.set_value("list-two", GLib.Variant("i", self._list_two.get_selected_id()))

    def show_playlist_manager(self, object_id, genre_id, is_album):
        """
            Show playlist manager for object_id
            Current view stay present in ViewContainer
            @param object id as int
            @param genre id as int
            @param is_album as bool
        """
        view = PlaylistsManageView(object_id, genre_id, is_album)
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)

    def show_playlist_editor(self, playlist_name):
        """
            Show playlist editor for playlist
            Current view stay present in ViewContainer
            @param playlist name as str
        """
        view = PlaylistEditView(playlist_name)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        view.populate()

    def main_widget(self):
        """
            Get main widget
            @return Gtk.HPaned
        """
        return self._paned_main_list

    def stop_all(self):
        """
            Stop current view from processing
        """
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    def show_genres(self, show):
        """
            Show/Hide genres
            @param bool
        """
        self._show_genres = show
        self._list_one.clear()
        self._update_list_one(None)

    def destroy_current_view(self):
        """
            Destroy current view
        """
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    def update_view(self):
        """
            Update current view
        """
        view = self._stack.get_visible_child()
        if view:
            view.update_covers()

    def on_scan_finished(self, scanner):
        """
            Mark force scan as False, update lists
            @param scanner as CollectionScanner
        """
        self._update_lists(scanner)

    ############
    # Private  #
    ############
    def _setup_view(self):
        """
            Setup window main view:
                - genre list
                - artist list
                - main view as artist view or album view
        """
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList()
        self._list_one.show()
        self._list_two = SelectionList()
        self._list_one.connect("item-selected", self._on_list_one_selected)
        self._list_one.connect("populated", self._on_list_populated)
        self._list_two.connect("item-selected", self._on_list_two_selected)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property("hexpand", True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        separator = Gtk.Separator()
        separator.show()
        self._paned_list_view.add1(self._list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(Lp.settings.get_value("paned-mainlist-width").get_int32())
        self._paned_list_view.set_position(Lp.settings.get_value("paned-listview-width").get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    def _get_saved_view_state(self):
        """
            Get save view state
            @return (list one id, list two id)
        """
        list_one_id = Type.POPULARS
        list_two_id = Type.NONE
        if Lp.settings.get_value("save-state"):
            position = Lp.settings.get_value("list-one").get_int32()
            if position != -1:
                list_one_id = position
            position = Lp.settings.get_value("list-two").get_int32()
            if position != -1:
                list_two_id = position

        return (list_one_id, list_two_id)

    def _add_genre(self, scanner, genre_id):
        """
            Add genre to genre list
            @param scanner as CollectionScanner
            @param genre id as int
        """
        if self._show_genres:
            genre_name = Lp.genres.get_name(genre_id)
            self._list_one.add_value((genre_id, genre_name))

    def _add_artist(self, scanner, artist_id, album_id):
        """
            Add artist to artist list
            @param scanner as CollectionScanner
            @param artist id as int
            @param album id as int
        """
        artist_name = Lp.artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Lp.albums.get_genre_ids(album_id)
            genre_ids.append(Type.ALL)
            if self._list_one.get_selected_id() in genre_ids:
                self._list_two.add_value((artist_id, artist_name))
        else:
            self._list_one.add_value((artist_id, artist_name))

    def _setup_scanner(self):
        """
            Run collection update if needed
            @return True if hard scan is running
        """
        Lp.scanner.connect("scan-finished", self.on_scan_finished)
        Lp.scanner.connect("genre-update", self._add_genre)
        Lp.scanner.connect("artist-update", self._add_artist)

    def _update_lists(self, updater=None):
        """
            Update lists
            @param updater as GObject
        """
        self._update_list_one(updater)
        self._update_list_two(updater)

    def _update_list_one(self, updater):
        """
            Update list one
            @param updater as GObject
        """
        update = updater is not None
        # Do not update if updater is PlaylistsManager
        if not isinstance(updater, PlaylistsManager):
            if self._show_genres:
                self._setup_list_genres(self._list_one, update)
            else:
                self._setup_list_artists(self._list_one, Type.ALL, update)

    def _update_list_two(self, updater):
        """
            Update list two
            @param updater as GObject
        """
        update = updater is not None
        object_id = self._list_one.get_selected_id()
        if object_id == Type.PLAYLISTS:
            self._setup_list_playlists(update)
        elif self._show_genres and object_id != Type.NONE:
            self._setup_list_artists(self._list_two, object_id, update)

    def _get_headers(self):
        """
            Return list one headers
        """
        items = []
        items.append((Type.POPULARS, _("Popular albums")))
        items.append((Type.RECENTS, _("Recently added albums")))
        items.append((Type.RANDOMS, _("Random albums")))
        items.append((Type.PLAYLISTS, _("Playlists")))
        items.append((Type.RADIOS, _("Radios")))
        if self._show_genres:
            items.append((Type.ALL, _("All artists")))
        else:
            items.append((Type.ALL, _("All albums")))
        return items

    def _setup_list_genres(self, selection_list, update):
        """
            Setup list for genres
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """

        def load():
            sql = Lp.db.get_cursor()
            genres = Lp.genres.get(sql)
            sql.close()
            return genres

        def setup(genres):
            items = self._get_headers()
            items.append((Type.SEPARATOR, ""))
            items += genres
            selection_list.mark_as_artists(False)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        loader = Loader(target=load, view=selection_list, on_finished=setup)
        loader.start()

    def _setup_list_artists(self, selection_list, genre_id, update):
        """
            Setup list for artists
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """

        def load():
            sql = Lp.db.get_cursor()
            artists = Lp.artists.get(genre_id, sql)
            compilations = Lp.albums.get_compilations(genre_id, sql)
            sql.close()
            return (artists, compilations)

        def setup(artists, compilations):
            if selection_list == self._list_one:
                items = self._get_headers()
                items.append((Type.SEPARATOR, ""))
            else:
                items = []
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
            items += artists
            selection_list.mark_as_artists(True)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.hide()
            self._list_two_restore = Type.NONE
        loader = Loader(target=load, view=selection_list, on_finished=lambda r: setup(*r))
        loader.start()

    def _setup_list_playlists(self, update):
        """
            Setup list for playlists
            @param update as bool
            @thread safe
        """
        playlists = [(Type.LOVED, Lp.playlists._LOVED)]
        playlists += [(Type.POPULARS, _("Popular tracks"))]
        playlists += [(Type.RECENTS, _("Recently played"))]
        playlists += [(Type.NEVER, _("Never played"))]
        playlists += [(Type.RANDOMS, _("Random tracks"))]
        playlists.append((Type.SEPARATOR, ""))
        playlists += Lp.playlists.get()
        if update:
            self._list_two.update_values(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)

    def _update_view_device(self, device_id):
        """
            Update current view with device view,
            Use existing view if available
            @param device id as int
        """
        device = self._devices[device_id]

        child = self._stack.get_child_by_name(device.uri)
        if child is None:
            child = DeviceView(device, self._progress)
            self._stack.add_named(child, device.uri)
            child.show()
        child.populate()
        self._stack.set_visible_child(child)
        self._stack.clean_old_views(child)

    def _update_view_artists(self, artist_id, genre_id):
        """
            Update current view with artists view
            @param artist id as int
            @param genre id as int
        """

        def load():
            sql = Lp.db.get_cursor()
            if artist_id == Type.COMPILATIONS:
                albums = Lp.albums.get_compilations(genre_id, sql)
            elif genre_id == Type.ALL:
                albums = Lp.albums.get_ids(artist_id, None, sql)
            else:
                albums = Lp.albums.get_ids(artist_id, genre_id, sql)
            sql.close()
            return albums

        view = ArtistView(artist_id, genre_id)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_albums(self, genre_id, is_compilation=False):
        """
            Update current view with albums view
            @param genre id as int
            @param is compilation as bool
        """

        def load():
            sql = Lp.db.get_cursor()
            albums = []
            if genre_id == Type.ALL:
                if is_compilation:
                    albums = Lp.albums.get_compilations(None, sql)
                else:
                    if Lp.settings.get_value("show-compilations"):
                        albums = Lp.albums.get_compilations(None, sql)
                    albums += Lp.albums.get_ids(None, None, sql)
            elif genre_id == Type.POPULARS:
                albums = Lp.albums.get_populars(sql)
            elif genre_id == Type.RECENTS:
                albums = Lp.albums.get_recents(sql)
            elif genre_id == Type.RANDOMS:
                albums = Lp.albums.get_randoms(sql)
            elif is_compilation:
                albums = Lp.albums.get_compilations(genre_id, sql)
            else:
                if Lp.settings.get_value("show-compilations"):
                    albums = Lp.albums.get_compilations(genre_id, sql)
                albums += Lp.albums.get_ids(None, genre_id, sql)
            sql.close()
            return albums

        view = AlbumsView(genre_id, is_compilation)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_playlists(self, playlist_id):
        """
            Update current view with playlist view
            @param playlist id as int
        """

        def load():
            sql = Lp.db.get_cursor()
            if playlist_id == Lp.player.get_user_playlist_id():
                tracks = [t.id for t in Lp.player.get_user_playlist()]
            elif playlist_id == Type.POPULARS:
                tracks = Lp.tracks.get_populars(sql)
            elif playlist_id == Type.RECENTS:
                tracks = Lp.tracks.get_recently_listened_to(sql)
            elif playlist_id == Type.NEVER:
                tracks = Lp.tracks.get_never_listened_to(sql)
            elif playlist_id == Type.RANDOMS:
                tracks = Lp.tracks.get_randoms(sql)
            else:
                tracks = Lp.playlists.get_tracks_id(name, sql)
            sql.close()
            return tracks

        view = None
        if playlist_id is not None:
            name = self._list_two.get_value(playlist_id)
            view = PlaylistView(playlist_id, name, self._stack)
        else:
            view = PlaylistsManageView(-1, None, False)
        if view:
            # Management or user playlist
            if playlist_id is None:
                view.populate()
            else:
                loader = Loader(target=load, view=view)
                loader.start()
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    def _update_view_radios(self):
        """
            Update current view with radios view
        """
        view = RadiosView()
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _add_device(self, volume):
        """
            Add volume to device list
            @param volume as Gio.Volume
        """
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        # Just to be sure
        if uri is not None and len(uri) > 1 and uri[-1:] != "/":
            uri += "/"
        if uri is not None and uri.find("mtp:") != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = volume.get_name()
            dev.uri = uri
            self._devices[self._devices_index] = dev
            self._list_one.add_value((dev.id, dev.name))

    def _remove_device(self, volume):
        """
            Remove volume from device list
            @param volume as Gio.Volume
        """
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        for dev in self._devices.values():
            if dev.uri == uri:
                self._list_one.remove(dev.id)
                child = self._stack.get_child_by_name(uri)
                if child is not None:
                    child.destroy()
                del self._devices[dev.id]
            break

    def _on_list_one_selected(self, selection_list, selected_id):
        """
            Update view based on selected object
            @param list as SelectionList
            @param selected id as int
        """
        if selected_id == Type.PLAYLISTS:
            self._list_two.clear()
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_playlists(None)
            self._setup_list_playlists(False)
        elif selected_id < Type.DEVICES:
            self._list_two.hide()
            if not self._list_two.will_be_selected():
                self._update_view_device(selected_id)
        elif selected_id in [Type.POPULARS, Type.RECENTS, Type.RANDOMS]:
            self._list_two.hide()
            self._update_view_albums(selected_id)
        elif selected_id == Type.RADIOS:
            self._list_two.hide()
            self._update_view_radios()
        elif selection_list.is_marked_as_artists():
            self._list_two.hide()
            if selected_id == Type.ALL:
                self._update_view_albums(selected_id)
            elif selected_id == Type.COMPILATIONS:
                self._update_view_albums(None, True)
            else:
                self._update_view_artists(selected_id, None)
        else:
            self._list_two.clear()
            self._setup_list_artists(self._list_two, selected_id, False)
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_albums(selected_id, False)

    def _on_list_populated(self, selection_list):
        """
            Add device to list one and update db
            @param selection list as SelectionList
        """
        for dev in self._devices.values():
            self._list_one.add_value((dev.id, dev.name))

    def _on_list_two_selected(self, selection_list, selected_id):
        """
            Update view based on selected object
            @param list as SelectionList
            @param selected id as int
        """
        genre_id = self._list_one.get_selected_id()
        if genre_id == Type.PLAYLISTS:
            self._update_view_playlists(selected_id)
        elif selected_id == Type.COMPILATIONS:
            self._update_view_albums(genre_id, True)
        else:
            self._update_view_artists(selected_id, genre_id)

    def _on_mount_added(self, vm, mnt):
        """
            On volume mounter
            @param vm as Gio.VolumeMonitor
            @param mnt as Gio.Mount
        """
        self._add_device(mnt.get_volume())

    def _on_mount_removed(self, vm, mnt):
        """
            On volume removed, clean selection list
            @param vm as Gio.VolumeMonitor
            @param mnt as Gio.Mount
        """
        self._remove_device(mnt.get_volume())
Exemple #32
0
class AlbumsPopover(Gtk.Popover):
    """
        Popover => CurrentArtistView
    """

    def __init__(self):
        """
            Init popover
        """
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_id = None
        self.add(self._stack)

        Lp.player.connect("current-changed", self._update_content)

    def populate(self):
        """
            Load content of the popover
        """
        if Lp.player.current_track.album_artist_id == Type.COMPILATIONS:
            new_id = Lp.player.current_track.album_id
        else:
            new_id = Lp.player.current_track.album_artist_id
        if self._on_screen_id != new_id:
            self._on_screen_id = new_id
            view = CurrentArtistView(Lp.player.current_track.album_artist_id)
            albums = self._get_albums(Lp.player.current_track.album_artist_id)
            view.populate(albums)
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    def do_show(self):
        """
            Resize popover
        """
        size_setting = Lp.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and\
           isinstance(size_setting[1], int):
            self.set_size_request(size_setting[0]*0.65, size_setting[1]*0.8)
        else:
            self.set_size_request(600, 600)
        Gtk.Popover.do_show(self)

    def do_hide(self):
        """
            Remove view
        """
        Gtk.Popover.do_hide(self)
        child = self._stack.get_visible_child()
        if child is not None:
            child.stop()
        self._on_screen_id = None
        self._stack.clean_old_views(None)

#######################
# PRIVATE             #
#######################
    def _get_albums(self, artist_id):
        """
            Get albums
            @return album ids as [int]
        """
        sql = Lp.db.get_cursor()
        if artist_id == Type.COMPILATIONS:
            albums = [Lp.player.current_track.album_id]
        else:
            albums = Lp.artists.get_albums(artist_id, sql)
        sql.close()
        return albums

    def _update_content(self, player):
        """
            Update the content view
            @param player as Player
            @param track id as int
        """
        if self.is_visible():
            self.populate()

    def do_get_preferred_width(self):
        """
            Set 0 to force popover to not expand
        """
        return (0, 0)
Exemple #33
0
class AlbumsView(LazyLoadingView):
    """
        Show albums in a box
    """

    def __init__(self, genre_ids, artist_ids):
        """
            Init album view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        LazyLoadingView.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_ids = genre_ids
        self._artist_ids = artist_ids
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.set_property('height-request', 10)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self, albums):
        """
            Populate albums
            @param is compilation as bool
        """
        self._add_albums(albums)

    def show_context(self, show):
        """
            Hide context widget
            @param show as bool
        """
        if show:
            if self._context_widget is not None:
                self._context.show()
        elif self._context_widget is not None:
            self._context.hide()

    def stop(self):
        """
            Stop loading
        """
        self._lazy_queue = []
        for child in self._get_children():
            child.stop()

#######################
# PRIVATE             #
#######################
    def _update_overlays(self, widgets):
        """
            Update children's overlay
            @param widgets as AlbumWidget
        """
        if self._context_widget in widgets:
            widgets.remove(self._context_widget)
        View._update_overlays(self, widgets)

    def _init_context_position(self):
        """
            Init context position if needed
            See _on_position_notify()
        """
        if self._paned.get_position() == 0:
            height = Lp().settings.get_value(
                                            'paned-context-height').get_int32()
            # We set a stupid max value, safe as self._context is shrinked
            if height == -1:
                height = Lp().window.get_allocated_height()
            else:
                height = self._paned.get_allocated_height() - height
            self._paned.set_position(height)

    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumContextWidget(album_id,
                                                  self._genre_ids,
                                                  self._artist_ids,
                                                  size_group)
        self._context_widget.connect('populated',
                                     self._on_context_populated)
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)
        # We delay populate() to be sure widget get it size allocated
        GLib.idle_add(self._context_widget.populate)

    def _add_albums(self, albums):
        """
            Add albums to the view
            Start lazy loading
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0),
                                       self._genre_ids,
                                       self._artist_ids)
            self._albumbox.insert(widget, -1)
            widget.show()
            self._lazy_queue.append(widget)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False
            GLib.idle_add(self.lazy_loading)
            if self._viewport.get_child() is None:
                self._viewport.add(self._albumbox)

    def _on_context_populated(self, widget):
        """
            Populate again if needed
            @param widget as AlbumContextWidget
        """
        if not widget.is_populated():
            widget.populate()
        elif not self._stop:
            GLib.idle_add(self._context_widget.populate)
        else:
            self._stop = False

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        # we want position of context, not of main view,
        # because we do not get position notify if context hidden
        position = paned.get_allocated_height() - paned.get_position()
        Lp().settings.set_value('paned-context-height',
                                GLib.Variant('i', position))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @param child as Gtk.FlowboxChild
        """
        album_widget = child.get_child()
        if self._press_rect is None:
            if self._context_album_id == album_widget.get_id():
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            else:
                self._init_context_position()
                self._context_album_id = album_widget.get_id()
                self._populate_context(self._context_album_id)
                self._context.show()
        else:
            if self._context_album_id is not None:
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            popover = AlbumPopoverWidget(album_widget.get_id(),
                                         self._genre_ids,
                                         self._artist_ids)
            popover.set_relative_to(album_widget)
            popover.set_pointing_to(self._press_rect)
            album_widget.update_overlay()
            popover.show()

    def _on_button_press(self, flowbox, event):
        """
            Store pressed button
            @param flowbox as Gtk.Flowbox
            @param event as Gdk.EventButton
        """
        if event.button == 1:
            self._press_rect = None
        else:
            self._press_rect = Gdk.Rectangle()
            self._press_rect.x = event.x
            self._press_rect.y = event.y
            self._press_rect.width = self._press_rect.height = 1
            event.button = 1
Exemple #34
0
class RadioPopover(Gtk.Popover):
    """
        Popover with radio logos from the web
    """
    def __init__(self, name, radios_manager):
        """
            Init Popover
            @param name as string
            @param radios_manager as RadiosManager
        """
        Gtk.Popover.__init__(self)
        self._name = name
        self._radios_manager = radios_manager
        self._start = 0
        self._orig_pixbufs = {}

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui')
        builder.connect_signals(self)

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect('child-activated', self._on_activate)
        self._view.set_max_children_per_line(100)
        self._view.set_property('row-spacing', 10)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._logo = builder.get_object('logo')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._name_entry = builder.get_object('name')
        self._uri_entry = builder.get_object('uri')
        self._btn_add_modify = builder.get_object('btn_add_modify')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._logo)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._widget)
        self.add(self._stack)

        if self._name == '':
            builder.get_object('btn_add_modify').set_label(_("Add"))
        else:
            builder.get_object('btn_add_modify').set_label(_("Modify"))
            builder.get_object('btn_delete').show()
            self._name_entry.set_text(self._name)
            uris = self._radios_manager.get_tracks(self._name)
            if len(uris) > 0:
                self._uri_entry.set_text(uris[0])

    def do_show(self):
        """
            Resize popover and set signals callback
        """
        Gtk.Popover.do_show(self)
        self._name_entry.grab_focus()
        Lp.window.enable_global_shorcuts(False)

    def do_hide(self):
        """
            Kill thread
        """
        self._thread = False
        Gtk.Popover.do_hide(self)
        Lp.window.enable_global_shorcuts(True)

#######################
# PRIVATE             #
#######################

    def _populate_threaded(self):
        """
            Populate view
        """
        self._thread = True
        start_new_thread(self._populate, ())

    def _populate(self):
        """
            Same as _populate_threaded()
            @thread safe
        """
        self._urls = Lp.art.get_google_arts(self._name + "+logo+radio",
                                            self._start)
        if self._urls:
            self._start += GOOGLE_INC
            self._add_pixbufs()
        else:
            GLib.idle_add(self._show_not_found)

    def _add_pixbufs(self):
        """
            Add urls to the view
        """
        if self._urls:
            url = self._urls.pop()
            stream = None
            try:
                f = Gio.File.new_for_uri(url)
                (status, data, tag) = f.load_contents()
                if status:
                    stream = Gio.MemoryInputStream.new_from_data(data, None)
            except:
                if self._thread:
                    self._add_pixbufs()
            if stream:
                GLib.idle_add(self._add_pixbuf, stream)
            if self._thread:
                self._add_pixbufs()
        elif self._start < GOOGLE_MAX:
            self._populate_threaded()

    def _show_not_found(self):
        """
            Show not found message if view empty
        """
        if len(self._view.get_children()) == 0:
            self._stack.set_visible_child(self._not_found)
            self._stack.clean_old_views(self._not_found)

    def _add_pixbuf(self, stream):
        """
            Add stream to the view
        """
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
                stream, ArtSize.MONSTER, ArtSize.MONSTER, True, None)
            image = Gtk.Image()
            self._orig_pixbufs[image] = pixbuf
            # Scale preserving aspect ratio
            width = pixbuf.get_width()
            height = pixbuf.get_height()
            if width > height:
                height = height * ArtSize.BIG * self.get_scale_factor() / width
                width = ArtSize.BIG * self.get_scale_factor()
            else:
                width = width * ArtSize.BIG * self.get_scale_factor() / height
                height = ArtSize.BIG * self.get_scale_factor()
            scaled_pixbuf = pixbuf.scale_simple(width, height,
                                                GdkPixbuf.InterpType.BILINEAR)
            del pixbuf
            surface = Gdk.cairo_surface_create_from_pixbuf(
                scaled_pixbuf, 0, None)
            del scaled_pixbuf
            image.set_from_surface(surface)
            del surface
            image.show()
            self._view.add(image)
        except Exception as e:
            print(e)
            pass
        # Remove spinner if exist
        if self._spinner is not None:
            self._stack.set_visible_child(self._logo)
            self._stack.clean_old_views(self._logo)
            self._spinner = None

    def _on_btn_add_modify_clicked(self, widget):
        """
            Add/Modify a radio
            @param widget as Gtk.Widget
        """
        uri = self._uri_entry.get_text()
        new_name = self._name_entry.get_text()
        rename = self._name != '' and self._name != new_name

        if uri != '' and new_name != '':
            if rename:
                self._radios_manager.rename(new_name, self._name)
            else:
                self._radios_manager.add(new_name)
            self._radios_manager.add_track(new_name, uri.lstrip().rstrip())
            self._stack.remove(self._widget)
            self._stack.set_visible_child(self._spinner)
            self._name = new_name
            self._populate_threaded()
            self.set_size_request(700, 400)

    def _on_btn_delete_clicked(self, widget):
        """
            Delete a radio
            @param widget as Gtk.Widget
        """
        if self._name != '':
            cache = Art._RADIOS_PATH
            self._radios_manager.delete(self._name)
            Lp.art.clean_radio_cache(self._name)
            if os.path.exists(cache + "/%s.png" % self._name):
                os.remove(cache + "/%s.png" % self._name)

    def _on_activate(self, flowbox, child):
        """
            Use pixbuf as cover
            Reset cache and use player object to announce cover change
        """
        pixbuf = self._orig_pixbufs[child.get_child()]
        Lp.art.save_radio_logo(pixbuf, self._name)
        Lp.art.clean_radio_cache(self._name)
        Lp.art.announce_logo_update(self._name)
        self.hide()
        self._streams = {}

    def _on_entry_changed(self, entry):
        """
            Update modify/add button
            @param entry as Gtk.Entry
        """
        uri = self._uri_entry.get_text()
        name = self._name_entry.get_text()
        if name != '' and uri.find('://') != -1:
            self._btn_add_modify.set_sensitive(True)
        else:
            self._btn_add_modify.set_sensitive(False)
Exemple #35
0
class AlbumsView(View):
    """
        Show albums in a box
    """

    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None
        self._lazy_queue = []  # Widgets not initialized
        self._scroll_value = 0
        self._timeout_id = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.get_vadjustment().connect('value-changed',
                                                 self._on_value_changed)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp().settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp().window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self, albums):
        """
            Populate albums
            @param is compilation as bool
        """
        # Add first album to get album size,
        # used to precalculate next albums size
        if albums:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.init_widget()
            widget.show_all()
            self._albumbox.insert(widget, -1)
            # Keep album requisition
            self._requisition = widget.get_preferred_size()[1]
            self._add_albums(albums)

#######################
# PRIVATE             #
#######################
    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumContextWidget(album_id,
                                                  self._genre_id,
                                                  False,
                                                  size_group)
        self._context_widget.populate()
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    def _add_albums(self, albums):
        """
            Add albums to the view
            Start lazy loading
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0),
                                       self._requisition.width,
                                       self._requisition.height)
            self._albumbox.insert(widget, -1)
            widget.show_all()
            self._lazy_queue.append(widget)
            GLib.idle_add(self._add_albums, albums)
        else:
            GLib.idle_add(self._lazy_loading)
            self._viewport.add(self._albumbox)

    def _lazy_loading(self, widgets=[], scroll_value=0):
        """
            Load the view in a lazy way:
                - widgets first
                - _waiting_init then
            @param widgets as [AlbumSimpleWidgets]
            @param scroll_value as float
        """
        widget = None
        if self._stop or self._scroll_value != scroll_value:
            return
        if widgets:
            widget = widgets.pop(0)
            self._lazy_queue.remove(widget)
        elif self._lazy_queue:
            widget = self._lazy_queue.pop(0)
        if widget is not None:
            widget.init_widget()
            GLib.idle_add(self._lazy_loading, widgets, scroll_value)

    def _is_visible(self, widget):
        """
            Is widget visible in scrolled
            @param widget as Gtk.Widget
        """
        widget_alloc = widget.get_allocation()
        scrolled_alloc = self._scrolled.get_allocation()
        try:
            (x, y) = widget.translate_coordinates(self._scrolled, 0, 0)
            return (y > -widget_alloc.height-ArtSize.BIG or y >= 0) and\
                y < scrolled_alloc.height+ArtSize.BIG
        except:
            return True

    def _lazy_or_not(self):
        """
            Add visible widgets to lazy queue
        """
        self._timeout_id = None
        widgets = []
        for child in self._lazy_queue:
            if self._is_visible(child):
                widgets.append(child)
        GLib.idle_add(self._lazy_loading, widgets, self._scroll_value)

    def _on_value_changed(self, adj):
        """
            Update scroll value and check for lazy queue
            @param adj as Gtk.Adjustment
        """
        if self._timeout_id is not None:
            GLib.source_remove(self._timeout_id)
            self._timeout_id = None
        self._scroll_value = adj.get_value()
        if not self._lazy_queue:
            return
        self._timeout_id = GLib.timeout_add(250, self._lazy_or_not)

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        Lp().settings.set_value('paned-context-height',
                                GLib.Variant('i', paned.get_position()))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @param child as Gtk.FlowboxChild
        """
        album_widget = child.get_child()
        if self._press_rect is None:
            if self._context_album_id == album_widget.get_id():
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            else:
                if Lp().settings.get_value('auto-play'):
                    album = Album(album_widget.get_id())
                    track = Track(album.tracks_ids[0])
                    Lp().player.load(track)
                    Lp().player.set_albums(track.id, None,
                                           self._genre_id)
                else:
                    self._context_album_id = album_widget.get_id()
                    self._populate_context(self._context_album_id)
                    self._context.show()
        else:
            if self._context_album_id is not None:
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            popover = AlbumPopoverWidget(album_widget.get_id(),
                                         self._genre_id)
            popover.set_relative_to(album_widget)
            popover.set_pointing_to(self._press_rect)
            self._context_widget = popover.get_widget()
            popover.connect('destroy', self._on_popover_destroyed)
            popover.show()

    def _on_popover_destroyed(self, popover):
        """
            Remove from context
            @param popover as AlbumPopoverWidget
        """
        self._context_widget = None

    def _on_button_press(self, flowbox, event):
        """
            Store pressed button
            @param flowbox as Gtk.Flowbox
            @param event as Gdk.EventButton
        """
        if event.button == 1:
            self._press_rect = None
        else:
            self._press_rect = Gdk.Rectangle()
            self._press_rect.x = event.x
            self._press_rect.y = event.y
            self._press_rect.width = self._press_rect.height = 1
            event.button = 1
Exemple #36
0
class Container:
    def __init__(self):

        # Try to update db on start, will be done after list one poplating
        # finished
        self._need_to_update_db = True
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Navigation.DEVICES
        self._show_genres = Objects.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        self._list_one_restore = Navigation.POPULARS
        self._list_two_restore = Navigation.NONE
        if Objects.settings.get_value('save-state'):
            self._restore_view_state()

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Objects.playlists.connect("playlists-changed", self.update_lists)

    """
        Update db at startup only if needed
        @param force as bool to force update (if possible)
    """

    def update_db(self, force=False):
        # Stop previous scan
        if self._scanner.is_locked():
            self._scanner.stop()
            GLib.timeout_add(250, self.update_db, force)
        # Something is using progress bar, do nothing
        elif not self._progress.is_visible():
            if force:
                Objects.tracks.remove_outside()
                self._list_one_restore = self._list_one.get_selected_id()
                self._list_two_restore = self._list_two.get_selected_id()
                self.update_lists(True)
                self._scanner.update(False)
            elif Objects.tracks.is_empty():
                self._scanner.update(False)
            elif Objects.settings.get_value('startup-scan'):
                self._scanner.update(True)

    """
        Save view state
    """

    def save_view_state(self):
        Objects.settings.set_value(
            "list-one", GLib.Variant('i', self._list_one.get_selected_id()))
        Objects.settings.set_value(
            "list-two", GLib.Variant('i', self._list_two.get_selected_id()))

    """
        Show playlist manager for object_id
        Current view stay present in ViewContainer
        @param object id as int
        @param genre id as int
        @param is_album as bool
    """

    def show_playlist_manager(self, object_id, genre_id, is_album):
        view = PlaylistManageView(object_id, genre_id, is_album,
                                  self._stack.get_allocated_width() / 2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Show playlist editor for playlist
        Current view stay present in ViewContainer
        @param playlist name as str
    """

    def show_playlist_editor(self, playlist_name):
        view = PlaylistEditView(playlist_name,
                                self._stack.get_allocated_width() / 2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Update lists
        @param updater as GObject
    """

    def update_lists(self, updater=None):
        self._update_list_one(updater)
        self._update_list_two(updater)

    """
        Load external files
        @param files as [Gio.Files]
    """

    def load_external(self, files):
        self._need_to_update_db = False
        # We wait as selection list is threaded,
        # we don't want to insert item before populated
        # Same for locked db
        if self._list_one.is_populating() or\
           self._list_one.is_populating() or\
           self._scanner.is_locked():
            self._scanner.stop()
            GLib.timeout_add(250, self.load_external, files)
        else:
            self._scanner.add(files)

    """
        Get main widget
        @return Gtk.HPaned
    """

    def main_widget(self):
        return self._paned_main_list

    """
        Stop current view from processing
    """

    def stop_all(self):
        self._scanner.stop()
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    """
        Show/Hide genres
        @param bool
    """

    def show_genres(self, show):
        self._show_genres = show
        self._update_list_one(None)

    """
        Destroy current view
    """

    def destroy_current_view(self):
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    """
        Update current view
    """

    def update_view(self):
        view = self._stack.get_visible_child()
        if view:
            start_new_thread(view.update_covers, ())

############
# Private  #
############

    """
        Setup window main view:
            - genre list
            - artist list
            - main view as artist view or album view
    """
    def _setup_view(self):
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList()
        self._list_one.widget.show()
        self._list_two = SelectionList()
        self._list_one.connect('item-selected', self._on_list_one_selected)
        self._list_one.connect('populated', self._on_list_one_populated)
        self._list_two.connect('item-selected', self._on_list_two_selected)
        self._list_two.connect('populated', self._on_list_two_populated)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property('hexpand', True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        separator = Gtk.Separator()
        separator.show()
        self._paned_list_view.add1(self._list_two.widget)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one.widget)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Objects.settings.get_value("paned-mainlist-width").get_int32())
        self._paned_list_view.set_position(
            Objects.settings.get_value("paned-listview-width").get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    """
        Restore saved view
    """

    def _restore_view_state(self):
        position = Objects.settings.get_value('list-one').get_int32()
        if position != -1:
            self._list_one_restore = position
        else:
            self._list_one_restore = Navigation.POPULARS
        position = Objects.settings.get_value('list-two').get_int32()
        if position != -1:
            self._list_two_restore = position

    """
        Add genre to genre list
        @param scanner as CollectionScanner
        @param genre id as int
    """

    def _add_genre(self, scanner, genre_id):
        if self._show_genres:
            genre_name = Objects.genres.get_name(genre_id)
            self._list_one.add((genre_id, genre_name))

    """
        Add artist to artist list
        @param scanner as CollectionScanner
        @param artist id as int
        @param album id as int
    """

    def _add_artist(self, scanner, artist_id, album_id):
        artist_name = Objects.artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Objects.albums.get_genre_ids(album_id)
            genre_ids.append(Navigation.ALL)
            if self._list_one.get_selected_id() in genre_ids:
                self._list_two.add((artist_id, artist_name))
        else:
            self._list_one.add((artist_id, artist_name))

    """
        Run collection update if needed
        @return True if hard scan is running
    """

    def _setup_scanner(self):
        self._scanner = CollectionScanner(self._progress)
        self._scanner.connect("scan-finished", self._on_scan_finished)
        self._scanner.connect("genre-update", self._add_genre)
        self._scanner.connect("artist-update", self._add_artist)
        self._scanner.connect("added", self._play_track)

    """
        Update list one
        @param updater as GObject
    """

    def _update_list_one(self, updater):
        update = updater is not None
        # Do not update if updater is PlaylistsManager
        if not isinstance(updater, PlaylistsManager):
            if self._show_genres:
                self._setup_list_genres(self._list_one, update)
            else:
                self._setup_list_artists(self._list_one, Navigation.ALL,
                                         update)

    """
        Update list two
        @param updater as GObject
    """

    def _update_list_two(self, updater):
        update = updater is not None
        object_id = self._list_one.get_selected_id()

        if object_id == Navigation.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (update, ))
        elif self._show_genres and object_id != Navigation.NONE:
            self._setup_list_artists(self._list_two, object_id, update)

    """
        Return list one headers
    """

    def _get_headers(self):
        items = []
        items.append((Navigation.POPULARS, _("Popular albums")))
        items.append((Navigation.RECENTS, _("Recent albums")))
        items.append((Navigation.PLAYLISTS, _("Playlists")))
        if self._show_genres:
            items.append((Navigation.ALL, _("All artists")))
        else:
            items.append((Navigation.ALL, _("All albums")))
        return items

    """
        Setup list for genres
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """

    def _setup_list_genres(self, selection_list, update):
        sql = Objects.db.get_cursor()
        selection_list.mark_as_artists(False)
        items = self._get_headers() + Objects.genres.get(sql)
        if update:
            selection_list.update(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Hide list two base on current artist list
    """

    def _pre_setup_list_artists(self, selection_list):
        if selection_list == self._list_one:
            if self._list_two.widget.is_visible():
                self._list_two.widget.hide()
            self._list_two_restore = Navigation.NONE

    """
        Setup list for artists
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """

    def _setup_list_artists(self, selection_list, genre_id, update):
        GLib.idle_add(self._pre_setup_list_artists, selection_list)
        sql = Objects.db.get_cursor()
        items = []
        selection_list.mark_as_artists(True)
        if selection_list == self._list_one:
            items = self._get_headers()
        if Objects.albums.get_compilations(genre_id, sql):
            items.append((Navigation.COMPILATIONS, _("Compilations")))

        items += Objects.artists.get(genre_id, sql)

        if update:
            selection_list.update(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Setup list for playlists
        @param update as bool
        @thread safe
    """

    def _setup_list_playlists(self, update):
        playlists = Objects.playlists.get()
        if update:
            self._list_two.update(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)
            GLib.idle_add(self._update_view_playlists, None)

    """
        Update current view with device view,
        Use existing view if available
        @param object id as int
    """

    def _update_view_device(self, object_id):
        device = self._devices[object_id]

        if device and device.view:
            view = device.view
        else:
            view = DeviceView(device, self._progress,
                              self._stack.get_allocated_width() / 2)
            device.view = view
            view.show()
            start_new_thread(view.populate, ())
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with artists view
        @param object id as int
        @param genre id as int
    """

    def _update_view_artists(self, object_id, genre_id):
        view = ArtistView(object_id, True)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, (genre_id, ))
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with albums view
        @param object id as int
        @param genre id as int
    """

    def _update_view_albums(self, object_id, genre_id):
        view = AlbumView(object_id)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, (genre_id, ))
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with playlist view
        @param playlist id as int
    """

    def _update_view_playlists(self, playlist_id):
        view = None
        if playlist_id is not None:
            for (p_id, p_str) in Objects.playlists.get():
                if p_id == playlist_id:
                    view = PlaylistView(p_str, self._stack)
                    break
        else:
            view = PlaylistManageView(-1, None, False,
                                      self._stack.get_allocated_width() / 2)
        if view:
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            start_new_thread(view.populate, ())
            self._stack.clean_old_views(view)

    """
        Add volume to device list
        @param volume as Gio.Volume
    """

    def _add_device(self, volume):
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return
        path = root.get_path()
        if path and path.find('mtp:') != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = volume.get_name()
            dev.path = path
            self._devices[self._devices_index] = dev
            self._list_one.add_device(dev.name, dev.id)

    """
        Remove volume from device list
        @param volume as Gio.Volume
    """

    def _remove_device(self, volume):
        for dev in self._devices.values():
            if not os.path.exists(dev.path):
                self._list_one.remove(dev.id)
                device = self._devices[dev.id]
                if device.view:
                    device.view.destroy()
                del self._devices[dev.id]
            break

    """
        Update view based on selected object
        @param list as SelectionList
        @param object id as int
    """

    def _on_list_one_selected(self, selection_list, object_id):
        if object_id == Navigation.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (False, ))
            self._list_two.widget.show()
        elif object_id < Navigation.DEVICES:
            self._list_two.widget.hide()
            self._update_view_device(object_id)
        elif object_id in [Navigation.POPULARS, Navigation.RECENTS]:
            self._list_two.widget.hide()
            self._update_view_albums(object_id, None)
        elif selection_list.is_marked_as_artists():
            self._list_two.widget.hide()
            if object_id == Navigation.ALL or\
               object_id == Navigation.COMPILATIONS:
                self._update_view_albums(object_id, None)
            else:
                self._update_view_artists(object_id, None)
        else:
            start_new_thread(self._setup_list_artists,
                             (self._list_two, object_id, False))
            self._list_two.widget.show()
            if self._list_two_restore == Navigation.NONE:
                self._update_view_albums(object_id, None)

    """
        Restore previous state
        @param selection list as SelectionList
    """

    def _on_list_one_populated(self, selection_list):
        for dev in self._devices.values():
            self._list_one.add_device(dev.name, dev.id)
        if self._list_one_restore is not None:
            self._list_one.select_id(self._list_one_restore)
            self._list_one_restore = None
        if self._need_to_update_db:
            self._need_to_update_db = False
            self.update_db()

    """
        Update view based on selected object
        @param list as SelectionList
        @param object id as int
    """

    def _on_list_two_selected(self, selection_list, object_id):
        selected_id = self._list_one.get_selected_id()
        if selected_id == Navigation.PLAYLISTS:
            self._update_view_playlists(object_id)
        elif object_id == Navigation.COMPILATIONS:
            self._update_view_albums(object_id, selected_id)
        else:
            self._update_view_artists(object_id, selected_id)

    """
        Restore previous state
        @param selection list as SelectionList
    """

    def _on_list_two_populated(self, selection_list):
        if self._list_two_restore != Navigation.NONE:
            self._list_two.select_id(self._list_two_restore)
            self._list_two_restore = Navigation.NONE

    """
        Play tracks as user playlist
        @param scanner as collection scanner
        @param track id as int
        @param play as bool
    """

    def _play_track(self, scanner, track_id, play):
        tracks = [track_id]
        if play:
            Objects.player.clear_user_playlist()
            if not Objects.player.is_party():
                Objects.player.set_user_playlist(tracks, track_id)
            Objects.player.load(track_id)
        elif not Objects.player.is_party():
            Objects.player.add_to_user_playlist(track_id)

    """
        Mark force scan as False, update lists
        @param scanner as CollectionScanner
    """

    def _on_scan_finished(self, scanner):
        if self._list_one.is_populating() or self._list_two.is_populating():
            GLib.timeout_add(500, self._on_scan_finished, scanner)
        else:
            self.update_lists(scanner)

    """
        On volume mounter
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """

    def _on_mount_added(self, vm, mnt):
        self._add_device(mnt.get_volume())

    """
        On volume removed, clean selection list
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """

    def _on_mount_removed(self, vm, mnt):
        self._remove_device(mnt.get_volume())
Exemple #37
0
class Container:
    """
        Container for main view
    """
    def __init__(self):
        """
            Init container
        """
        self._pulse_timeout = None
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp().settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_ids, list_two_ids) = self._get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self._list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self._list_two.select_ids(list_two_ids)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)
        for mount in self._vm.get_mounts():
            self._add_device(mount, False)

        Lp().playlists.connect('playlists-changed',
                               self._update_playlists)

    def update_db(self):
        """
            Update db at startup only if needed
        """
        # Stop previous scan
        if Lp().scanner.is_locked():
            Lp().scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            # Something (device manager) is using progress bar
            if not self._progress.is_visible():
                Lp().scanner.update(self._progress)

    def get_genre_id(self):
        """
            Return current selected genre
            @return genre id as int
        """
        if self._show_genres:
            return self._list_one.get_selected_id()
        else:
            return None

    def init_list_one(self):
        """
            Init list one
        """
        self._update_list_one(None)

    def save_view_state(self):
        """
            Save view state
        """
        Lp().settings.set_value(
                            "list-one-ids",
                            GLib.Variant('ai',
                                         self._list_one.get_selected_ids()))
        Lp().settings.set_value(
                            "list-two-ids",
                            GLib.Variant('ai',
                                         self._list_two.get_selected_ids()))

    def show_playlist_manager(self, object_id, genre_ids,
                              artist_ids, is_album):
        """
            Show playlist manager for object_id
            Current view stay present in ViewContainer
            @param object id as int
            @param genre ids as [int]
            @param artist ids as [int]
            @param is_album as bool
        """
        view = PlaylistsManageView(object_id, genre_ids, artist_ids, is_album)
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)

    def show_playlist_editor(self, playlist_id):
        """
            Show playlist editor for playlist
            Current view stay present in ViewContainer
            @param playlist id as int
            @param playlist name as str
        """
        view = PlaylistEditView(playlist_id)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)
        view.populate()

    def main_widget(self):
        """
            Get main widget
            @return Gtk.HPaned
        """
        return self._paned_main_list

    def get_view_width(self):
        """
            Return view width
            @return width as int
        """
        return self._stack.get_allocation().width

    def stop_all(self):
        """
            Stop current view from processing
        """
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    def show_genres(self, show):
        """
            Show/Hide genres
            @param bool
        """
        self._show_genres = show
        self._list_one.clear()
        self._update_list_one(None)

    def destroy_current_view(self):
        """
            Destroy current view
        """
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    def disable_overlays(self):
        """
            Disable overlays
        """
        view = self._stack.get_visible_child()
        if view:
            view.disable_overlays()

    def update_view(self):
        """
            Update current view
        """
        view = self._stack.get_visible_child()
        if view:
            view.update_children()

    def reload_view(self):
        """
            Reload current view
        """
        values_two = self._list_two.get_selected_ids()
        if not values_two:
            values_one = self._list_one.get_selected_ids()
            if not values_one:
                values_one = [Type.POPULARS]
            self._list_one.select_ids([])
            self._list_one.select_ids(values_one)
        if self._list_two.is_visible():
            self._list_two.select_ids([])
            self._list_two.select_ids(values_two)

    def pulse(self, pulse):
        """
            Make progress bar visible/pulse if pulse is True
            @param pulse as bool
        """
        if pulse and not self._progress.is_visible():
            self._progress.show()
            if self._pulse_timeout is None:
                self._pulse_timeout = GLib.timeout_add(500, self._pulse)
        else:
            if self._pulse_timeout is not None:
                GLib.source_remove(self._pulse_timeout)
                self._pulse_timeout = None
                self._progress.hide()

    def on_scan_finished(self, scanner):
        """
            Mark force scan as False, update lists
            @param scanner as CollectionScanner
        """
        self._update_lists(scanner)

    def add_fake_phone(self):
        """
            Emulate an Android Phone
        """
        self._devices_index -= 1
        dev = Device()
        dev.id = self._devices_index
        dev.name = "Android phone"
        dev.uri = "file:///tmp/android/"
        d = Gio.File.new_for_uri(dev.uri+"Internal Memory")
        if not d.query_exists(None):
            d.make_directory_with_parents(None)
        d = Gio.File.new_for_uri(dev.uri+"SD Card")
        if not d.query_exists(None):
            d.make_directory_with_parents(None)
        self._devices[self._devices_index] = dev

############
# Private  #
############
    def _pulse(self):
        """
            Make progress bar pulse while visible
            @param pulse as bool
        """
        if self._progress.is_visible() and not Lp().scanner.is_locked():
            self._progress.pulse()
            return True
        else:
            self._progress.set_fraction(0.0)
            return False

    def _setup_view(self):
        """
            Setup window main view:
                - genre list
                - artist list
                - main view as artist view or album view
        """
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList(SelectionMode.LIMITED)
        self._list_one.show()
        self._list_two = SelectionList(SelectionMode.NORMAL)
        self._list_one.connect('item-selected', self._on_list_one_selected)
        self._list_one.connect('populated', self._on_list_populated)
        self._list_two.connect('item-selected', self._on_list_two_selected)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property('hexpand', True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        self._paned_list_view.add1(self._list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Lp().settings.get_value('paned-mainlist-width').get_int32())
        self._paned_list_view.set_position(
            Lp().settings.get_value('paned-listview-width').get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    def _get_saved_view_state(self):
        """
            Get save view state
            @return (list one id, list two id)
        """
        list_one_ids = [Type.POPULARS]
        list_two_ids = [Type.NONE]
        if Lp().settings.get_value('save-state'):
            list_one_ids = []
            list_two_ids = []
            ids = Lp().settings.get_value('list-one-ids')
            for i in ids:
                if isinstance(i, int):
                    list_one_ids.append(i)
            ids = Lp().settings.get_value('list-two-ids')
            for i in ids:
                if isinstance(i, int):
                    list_two_ids.append(i)
        return (list_one_ids, list_two_ids)

    def _add_genre(self, scanner, genre_id):
        """
            Add genre to genre list
            @param scanner as CollectionScanner
            @param genre id as int
        """
        if self._show_genres:
            genre_name = Lp().genres.get_name(genre_id)
            self._list_one.add_value((genre_id, genre_name))

    def _add_artist(self, scanner, artist_id, album_id):
        """
            Add artist to artist list
            @param scanner as CollectionScanner
            @param artist id as int
            @param album id as int
        """
        artist_name = Lp().artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Lp().albums.get_genre_ids(album_id)
            genre_ids.append(Type.ALL)
            for i in self._list_one.get_selected_ids():
                if i in genre_ids:
                    self._list_two.add_value((artist_id, artist_name))
        else:
            self._list_one.add_value((artist_id, artist_name))

    def _setup_scanner(self):
        """
            Run collection update if needed
            @return True if hard scan is running
        """
        Lp().scanner.connect('scan-finished', self.on_scan_finished)
        Lp().scanner.connect('genre-added', self._add_genre)
        Lp().scanner.connect('artist-added', self._add_artist)

    def _update_playlists(self, playlists, playlist_id):
        """
            Update playlists in second list
            @param playlists as Playlists
            @param playlist_id as int
        """
        ids = self._list_one.get_selected_ids()
        if ids and ids[0] == Type.PLAYLISTS:
            if Lp().playlists.exists(playlist_id):
                self._list_two.update_value(playlist_id,
                                            Lp().playlists.get_name(
                                                                  playlist_id))
            else:
                self._list_two.remove(playlist_id)

    def _update_lists(self, updater=None):
        """
            Update lists
            @param updater as GObject
        """
        self._update_list_one(updater)
        self._update_list_two(updater)

    def _update_list_one(self, updater):
        """
            Update list one
            @param updater as GObject
        """
        update = updater is not None
        if self._show_genres:
            self._setup_list_genres(self._list_one, update)
        else:
            self._setup_list_artists(self._list_one, [Type.ALL], update)

    def _update_list_two(self, updater):
        """
            Update list two
            @param updater as GObject
        """
        update = updater is not None
        ids = self._list_one.get_selected_ids()
        if ids and ids[0] == Type.PLAYLISTS:
            self._setup_list_playlists(update)
        elif self._show_genres and ids:
            self._setup_list_artists(self._list_two, ids, update)

    def _get_headers(self):
        """
            Return list one headers
        """
        items = []
        items.append((Type.POPULARS, _("Popular albums")))
        items.append((Type.RECENTS, _("Recently added albums")))
        items.append((Type.RANDOMS, _("Random albums")))
        items.append((Type.PLAYLISTS, _("Playlists")))
        items.append((Type.RADIOS, _("Radios")))
        if self._show_genres:
            items.append((Type.ALL, _("All artists")))
        else:
            items.append((Type.ALL, _("All albums")))
        return items

    def _setup_list_genres(self, selection_list, update):
        """
            Setup list for genres
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            genres = Lp().genres.get()
            return genres

        def setup(genres):
            items = self._get_headers()
            items.append((Type.SEPARATOR, ''))
            items += genres
            selection_list.mark_as_artists(False)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        loader = Loader(target=load, view=selection_list, on_finished=setup)
        loader.start()

    def _setup_list_artists(self, selection_list, genre_ids, update):
        """
            Setup list for artists
            @param list as SelectionList
            @param genre ids as [int]
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            artists = Lp().artists.get(genre_ids)
            compilations = Lp().albums.get_compilations(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            if selection_list == self._list_one:
                items = self._get_headers()
                items.append((Type.SEPARATOR, ''))
            else:
                items = []
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
            items += artists
            selection_list.mark_as_artists(True)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.hide()
            self._list_two_restore = Type.NONE
        loader = Loader(target=load, view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def _setup_list_playlists(self, update):
        """
            Setup list for playlists
            @param update as bool
            @thread safe
        """
        playlists = [(Type.LOVED, Lp().playlists._LOVED)]
        playlists.append((Type.POPULARS, _("Popular tracks")))
        playlists.append((Type.RECENTS, _("Recently played")))
        playlists.append((Type.NEVER, _("Never played")))
        playlists.append((Type.RANDOMS, _("Random tracks")))
        playlists.append((Type.SEPARATOR, ''))
        playlists += Lp().playlists.get()
        if update:
            self._list_two.update_values(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)

    def _update_view_device(self, device_id):
        """
            Update current view with device view,
            Use existing view if available
            @param device id as int
        """
        device = self._devices[device_id]
        child = self._stack.get_child_by_name(device.uri)
        if child is None:
            if DeviceView.get_files(device.uri):
                child = DeviceView(device, self._progress)
                self._stack.add_named(child, device.uri)
            else:
                child = DeviceLocked()
                self._stack.add(child)
            child.show()
        child.populate()
        self._stack.set_visible_child(child)
        self._stack.clean_old_views(child)

    def _update_view_artists(self, genre_ids, artist_ids):
        """
            Update current view with artists view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        def load():
            if genre_ids and genre_ids[0] == Type.ALL:
                albums = Lp().albums.get_ids(artist_ids, [])
            else:
                albums = []
                if artist_ids and artist_ids[0] == Type.COMPILATIONS:
                    albums += Lp().albums.get_compilations(genre_ids)
                albums += Lp().albums.get_ids(artist_ids, genre_ids)
            return albums

        view = ArtistView(artist_ids, genre_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_albums(self, genre_ids, artist_ids):
        """
            Update current view with albums view
            @param genre ids as [int]
            @param is compilation as bool
        """
        def load():
            albums = []
            is_compilation = artist_ids and artist_ids[0] == Type.COMPILATIONS
            if genre_ids and genre_ids[0] == Type.ALL:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    albums = Lp().albums.get_compilations()
                if not is_compilation:
                    albums += Lp().albums.get_ids()
            elif genre_ids and genre_ids[0] == Type.POPULARS:
                albums = Lp().albums.get_populars()
            elif genre_ids and genre_ids[0] == Type.RECENTS:
                albums = Lp().albums.get_recents()
            elif genre_ids and genre_ids[0] == Type.RANDOMS:
                albums = Lp().albums.get_randoms()
            else:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    albums = Lp().albums.get_compilations(genre_ids)
                if not is_compilation:
                    albums += Lp().albums.get_ids([], genre_ids)
            return albums

        view = AlbumsView(genre_ids, artist_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_playlists(self, playlist_ids=[]):
        """
            Update current view with playlist view
            @param playlist ids as [int]
        """
        def load():
            track_ids = []
            for playlist_id in playlist_ids:
                if playlist_id == Type.POPULARS:
                    tracks = Lp().tracks.get_populars()
                elif playlist_id == Type.RECENTS:
                    tracks = Lp().tracks.get_recently_listened_to()
                elif playlist_id == Type.NEVER:
                    tracks = Lp().tracks.get_never_listened_to()
                elif playlist_id == Type.RANDOMS:
                    tracks = Lp().tracks.get_randoms()
                else:
                    tracks = Lp().playlists.get_track_ids(playlist_id)
                for track_id in tracks:
                    if track_id not in track_ids:
                        track_ids.append(track_id)
            return track_ids

        view = None
        if playlist_ids:
            view = PlaylistsView(playlist_ids)
            loader = Loader(target=load, view=view)
            loader.start()
        else:
            view = PlaylistsManageView(Type.NONE, [], [], False)
            view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_radios(self):
        """
            Update current view with radios view
        """
        view = RadiosView()
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _add_device(self, mount, show=False):
        """
            Add a device
            @param mount as Gio.Mount
            @param show as bool
        """
        if mount.get_volume() is None:
            return
        name = mount.get_name()
        uri = mount.get_default_location().get_uri()
        # Add / to uri if needed
        if uri is not None and len(uri) > 1 and uri[-1:] != '/':
            uri += '/'

        if uri is not None and uri.find('mtp:') != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = name
            dev.uri = uri
            self._devices[self._devices_index] = dev
            if show:
                self._list_one.add_value((dev.id, dev.name))

    def _remove_device(self, mount):
        """
            Remove volume from device list
            @param mount as Gio.Mount
        """
        if mount.get_volume() is None:
            return
        uri = mount.get_default_location().get_uri()
        for dev in self._devices.values():
            if dev.uri == uri:
                self._list_one.remove(dev.id)
                child = self._stack.get_child_by_name(uri)
                if child is not None:
                    child.destroy()
                del self._devices[dev.id]
            break

    def _on_list_one_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        selected_ids = self._list_one.get_selected_ids()
        if not selected_ids:
            return
        if selected_ids[0] == Type.PLAYLISTS:
            self._list_two.clear()
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_playlists()
            self._setup_list_playlists(False)
        elif Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES:
            self._list_two.hide()
            if not self._list_two.will_be_selected():
                self._update_view_device(selected_ids[0])
        elif selected_ids[0] in [Type.POPULARS,
                                 Type.RECENTS,
                                 Type.RANDOMS]:
            self._list_two.hide()
            self._update_view_albums(selected_ids, [])
        elif selected_ids[0] == Type.RADIOS:
            self._list_two.hide()
            self._update_view_radios()
        elif selection_list.is_marked_as_artists():
            self._list_two.hide()
            if selected_ids[0] == Type.ALL:
                self._update_view_albums(selected_ids, [])
            elif selected_ids[0] == Type.COMPILATIONS:
                self._update_view_albums([], selected_ids)
            else:
                self._update_view_artists([], selected_ids)
        else:
            self._list_two.clear()
            self._setup_list_artists(self._list_two, selected_ids, False)
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_albums(selected_ids, [])

    def _on_list_populated(self, selection_list):
        """
            Add device to list one and update db
            @param selection list as SelectionList
        """
        for dev in self._devices.values():
            self._list_one.add_value((dev.id, dev.name))

    def _on_list_two_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        genre_ids = self._list_one.get_selected_ids()
        selected_ids = self._list_two.get_selected_ids()
        if not selected_ids or not genre_ids:
            return
        if genre_ids[0] == Type.PLAYLISTS:
            self._update_view_playlists(selected_ids)
        elif selected_ids[0] == Type.COMPILATIONS:
            self._update_view_albums(genre_ids, selected_ids)
        else:
            self._update_view_artists(genre_ids, selected_ids)

    def _on_mount_added(self, vm, mount):
        """
            On volume mounter
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self._add_device(mount, True)

    def _on_mount_removed(self, vm, mount):
        """
            On volume removed, clean selection list
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self._remove_device(mount)
Exemple #38
0
class Container:
    """
        Container for main view
    """
    def __init__(self):
        """
            Init container
        """
        self.__pulse_timeout = None
        # Index will start at -VOLUMES
        self.__devices = {}
        self.__devices_index = Type.DEVICES
        self.__show_genres = Lp().settings.get_value('show-genres')
        self.__stack = ViewContainer(500)
        self.__stack.show()

        self.__setup_view()
        self.__setup_scanner()

        (list_one_ids, list_two_ids) = self.__get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self.__list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self.__list_two.select_ids(list_two_ids)

        # Volume manager
        self.__vm = Gio.VolumeMonitor.get()
        self.__vm.connect('mount-added', self.__on_mount_added)
        self.__vm.connect('mount-removed', self.__on_mount_removed)
        for mount in self.__vm.get_mounts():
            self.__add_device(mount, False)

        Lp().playlists.connect('playlists-changed', self.__update_playlists)

    def update_db(self):
        """
            Update db at startup only if needed
        """
        # Stop previous scan
        if Lp().scanner.is_locked():
            Lp().scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            Lp().scanner.update()

    def get_genre_id(self):
        """
            Return current selected genre
            @return genre id as int
        """
        if self.__show_genres:
            return self.__list_one.get_selected_id()
        else:
            return None

    def init_list_one(self):
        """
            Init list one
        """
        self.__update_list_one(None)

    def save_view_state(self):
        """
            Save view state
        """
        Lp().settings.set_value(
            "list-one-ids", GLib.Variant('ai', self.__list_one.selected_ids))
        Lp().settings.set_value(
            "list-two-ids", GLib.Variant('ai', self.__list_two.selected_ids))

    def show_playlist_manager(self, object_id, genre_ids, artist_ids,
                              is_album):
        """
            Show playlist manager for object_id
            Current view stay present in ViewContainer
            @param object id as int
            @param genre ids as [int]
            @param artist ids as [int]
            @param is_album as bool
        """
        from lollypop.view_playlists import PlaylistsManageView
        current = self.__stack.get_visible_child()
        view = PlaylistsManageView(object_id, genre_ids, artist_ids, is_album)
        view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        current.disable_overlay()

    def show_playlist_editor(self, playlist_id):
        """
            Show playlist editor for playlist
            Current view stay present in ViewContainer
            @param playlist id as int
            @param playlist name as str
        """
        from lollypop.view_playlists import PlaylistEditView
        view = PlaylistEditView(playlist_id)
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)
        view.populate()

    def get_view_width(self):
        """
            Return view width
            @return width as int
        """
        return self.__stack.get_allocation().width

    def stop_all(self):
        """
            Stop current view from processing
        """
        view = self.__stack.get_visible_child()
        if view is not None:
            self.__stack.clean_old_views(None)

    def show_genres(self, show):
        """
            Show/Hide genres
            @param bool
        """
        self.__show_genres = show
        self.__list_one.clear()
        self.__list_two.clear()
        self.__list_two.hide()
        self.__update_list_one(None)
        self.__list_one.select_ids([Type.POPULARS])

    def destroy_current_view(self):
        """
            Destroy current view
        """
        view = self.__stack.get_visible_child()
        for child in self.__stack.get_children():
            if child != view:
                self.__stack.set_visible_child(child)
                self.__stack.clean_old_views(child)
                break

    @property
    def view(self):
        """
            Disable overlays
        """
        return self.__stack.get_visible_child()

    @property
    def progress(self):
        """
            Progress bar
            @return ProgressBar
        """
        return self.__progress

    def add_remove_from(self, value, list_one, add):
        """
            Add or remove value to list
            @param value as (int, str)
            @param list one as bool
            @param add as bool
        """
        if list_one:
            l = self.__list_one
        else:
            l = self.__list_two
        if add:
            l.add_value(value)
        else:
            l.remove_value(value[0])

    def reload_view(self):
        """
            Reload current view
        """
        values_two = self.__list_two.selected_ids
        values_one = self.__list_one.selected_ids
        if not values_one:
            values_one = [Type.POPULARS]
        self.__list_one.select_ids([])
        self.__list_one.clear()
        self.__update_list_one(None)
        self.__list_one.select_ids(values_one)
        if self.__list_two.is_visible():
            self.__list_two.select_ids([])
            self.__list_two.clear()
            self.__update_list_two(None)
            self.__list_two.select_ids(values_two)

    def pulse(self, pulse):
        """
            Make progress bar visible/pulse if pulse is True
            @param pulse as bool
        """
        if pulse and not self.__progress.is_visible():
            self.__progress.show()
            if self.__pulse_timeout is None:
                self.__pulse_timeout = GLib.timeout_add(500, self.__pulse)
        else:
            if self.__pulse_timeout is not None:
                GLib.source_remove(self.__pulse_timeout)
                self.__pulse_timeout = None
                self.__progress.hide()

    def on_scan_finished(self, scanner):
        """
            Mark force scan as False, update lists
            @param scanner as CollectionScanner
        """
        self.__update_lists(scanner)

    def add_fake_phone(self):
        """
            Emulate an Android Phone
        """
        self.__devices_index -= 1
        dev = Device()
        dev.id = self.__devices_index
        dev.name = "Android phone"
        dev.uri = "file:///tmp/android/"
        d = Gio.File.new_for_uri(dev.uri + "Internal Memory")
        if not d.query_exists():
            d.make_directory_with_parents()
        d = Gio.File.new_for_uri(dev.uri + "SD Card")
        if not d.query_exists():
            d.make_directory_with_parents()
        self.__devices[self.__devices_index] = dev

    def show_artists_albums(self, artist_ids):
        """
            Show albums from artists
        """
        self.__update_view_artists([], artist_ids)
        GLib.idle_add(self.__list_two.hide)
        GLib.idle_add(self.__list_one.select_ids, [])

############
# Private  #
############

    def __pulse(self):
        """
            Make progress bar pulse while visible
            @param pulse as bool
        """
        if self.__progress.is_visible() and not Lp().scanner.is_locked():
            self.__progress.pulse()
            return True
        else:
            self.__progress.set_fraction(0.0, self)
            return False

    def __setup_view(self):
        """
            Setup window main view:
                - genre list
                - artist list
                - main view as artist view or album view
        """
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self.__list_one = SelectionList(True)
        self.__list_one.show()
        self.__list_two = SelectionList(False)
        self.__list_one.connect('item-selected', self.__on_list_one_selected)
        self.__list_one.connect('populated', self.__on_list_populated)
        self.__list_two.connect('item-selected', self.__on_list_two_selected)

        self.__progress = ProgressBar()
        self.__progress.set_property('hexpand', True)

        vgrid.add(self.__stack)
        vgrid.add(self.__progress)
        vgrid.show()

        self._paned_list_view.add1(self.__list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self.__list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Lp().settings.get_value('paned-mainlist-width').get_int32())
        self._paned_list_view.set_position(
            Lp().settings.get_value('paned-listview-width').get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    def __get_saved_view_state(self):
        """
            Get save view state
            @return (list one id, list two id)
        """
        list_one_ids = [Type.POPULARS]
        list_two_ids = [Type.NONE]
        if Lp().settings.get_value('save-state'):
            list_one_ids = []
            list_two_ids = []
            ids = Lp().settings.get_value('list-one-ids')
            for i in ids:
                if isinstance(i, int):
                    list_one_ids.append(i)
            ids = Lp().settings.get_value('list-two-ids')
            for i in ids:
                if isinstance(i, int):
                    list_two_ids.append(i)
        return (list_one_ids, list_two_ids)

    def __setup_scanner(self):
        """
            Run collection update if needed
            @return True if hard scan is running
        """
        Lp().scanner.connect('scan-finished', self.on_scan_finished)
        Lp().scanner.connect('genre-updated', self.__on_genre_updated)
        Lp().scanner.connect('artist-updated', self.__on_artist_updated)

    def __update_playlists(self, playlists, playlist_id):
        """
            Update playlists in second list
            @param playlists as Playlists
            @param playlist_id as int
        """
        ids = self.__list_one.selected_ids
        if ids and ids[0] == Type.PLAYLISTS:
            if Lp().playlists.exists(playlist_id):
                self.__list_two.update_value(
                    playlist_id,
                    Lp().playlists.get_name(playlist_id))
            else:
                self.__list_two.remove_value(playlist_id)

    def __update_lists(self, updater=None):
        """
            Update lists
            @param updater as GObject
        """
        self.__update_list_one(updater)
        self.__update_list_two(updater)

    def __update_list_one(self, updater):
        """
            Update list one
            @param updater as GObject
        """
        update = updater is not None
        if self.__show_genres:
            self.__setup_list_genres(self.__list_one, update)
        else:
            self.__setup_list_artists(self.__list_one, [Type.ALL], update)

    def __update_list_two(self, updater):
        """
            Update list two
            @param updater as GObject
        """
        update = updater is not None
        ids = self.__list_one.selected_ids
        if ids and ids[0] == Type.PLAYLISTS:
            self.__setup_list_playlists(update)
        elif self.__show_genres and ids:
            self.__setup_list_artists(self.__list_two, ids, update)

    def __setup_list_genres(self, selection_list, update):
        """
            Setup list for genres
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            genres = Lp().genres.get()
            return genres

        def setup(genres):
            items = selection_list.get_headers()
            items.append((Type.SEPARATOR, ''))
            items += genres
            selection_list.mark_as_artists(False)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        loader = Loader(target=load, view=selection_list, on_finished=setup)
        loader.start()

    def __setup_list_artists(self, selection_list, genre_ids, update):
        """
            Setup list for artists
            @param list as SelectionList
            @param genre ids as [int]
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            artists = Lp().artists.get(genre_ids)
            compilations = Lp().albums.get_compilation_ids(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            if selection_list == self.__list_one:
                items = selection_list.get_headers()
                if not compilations:
                    items.append((Type.SEPARATOR, ''))
            else:
                items = []
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
                items.append((Type.SEPARATOR, ''))
            items += artists
            selection_list.mark_as_artists(True)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        if selection_list == self.__list_one:
            if self.__list_two.is_visible():
                self.__list_two.hide()
            self.__list_two_restore = Type.NONE
        loader = Loader(target=load,
                        view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def __setup_list_playlists(self, update):
        """
            Setup list for playlists
            @param update as bool
            @thread safe
        """
        playlists = [(Type.LOVED, Lp().playlists.LOVED)]
        playlists.append((Type.POPULARS, _("Popular tracks")))
        playlists.append((Type.RECENTS, _("Recently played")))
        playlists.append((Type.NEVER, _("Never played")))
        playlists.append((Type.RANDOMS, _("Random tracks")))
        playlists.append((Type.SEPARATOR, ''))
        playlists += Lp().playlists.get()
        if update:
            self.__list_two.update_values(playlists)
        else:
            self.__list_two.mark_as_artists(False)
            self.__list_two.populate(playlists)

    def __stop_current_view(self):
        """
            Stop current view
        """
        child = self.__stack.get_visible_child()
        if child is not None:
            if hasattr(child, "stop"):
                child.stop()

    def __update_view_device(self, device_id):
        """
            Update current view with device view,
            Use existing view if available
            @param device id as int
        """
        from lollypop.view_device import DeviceView, DeviceLocked
        self.__stop_current_view()
        device = self.__devices[device_id]
        child = self.__stack.get_child_by_name(device.uri)
        if child is None:
            files = DeviceView.get_files(device.uri)
            if files:
                if child is None:
                    child = DeviceView(device)
                    self.__stack.add_named(child, device.uri)
            else:
                child = DeviceLocked()
                self.__stack.add(child)
            child.show()
        child.populate()
        self.__stack.set_visible_child(child)
        self.__stack.clean_old_views(child)

    def __update_view_artists(self, genre_ids, artist_ids):
        """
            Update current view with artists view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        def load():
            if genre_ids and genre_ids[0] == Type.ALL:
                albums = Lp().albums.get_ids(artist_ids, [])
            else:
                albums = []
                if artist_ids and artist_ids[0] == Type.COMPILATIONS:
                    albums += Lp().albums.get_compilation_ids(genre_ids)
                albums += Lp().albums.get_ids(artist_ids, genre_ids)
            return albums

        from lollypop.view_artist import ArtistView
        self.__stop_current_view()
        view = ArtistView(artist_ids, genre_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_albums(self, genre_ids, artist_ids):
        """
            Update current view with albums view
            @param genre ids as [int]
            @param is compilation as bool
        """
        def load():
            albums = []
            is_compilation = artist_ids and artist_ids[0] == Type.COMPILATIONS
            if genre_ids and genre_ids[0] == Type.ALL:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    albums = Lp().albums.get_compilation_ids()
                if not is_compilation:
                    albums += Lp().albums.get_ids()
            elif genre_ids and genre_ids[0] == Type.POPULARS:
                albums = Lp().albums.get_populars()
            elif genre_ids and genre_ids[0] == Type.RECENTS:
                albums = Lp().albums.get_recents()
            elif genre_ids and genre_ids[0] == Type.RANDOMS:
                albums = Lp().albums.get_randoms()
            else:
                if is_compilation or\
                        Lp().settings.get_value('show-compilations'):
                    albums = Lp().albums.get_compilation_ids(genre_ids)
                if not is_compilation:
                    albums += Lp().albums.get_ids([], genre_ids)
            return albums

        from lollypop.view_albums import AlbumsView
        self.__stop_current_view()
        view = AlbumsView(genre_ids, artist_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_playlists(self, playlist_ids=[]):
        """
            Update current view with playlist view
            @param playlist ids as [int]
        """
        def load():
            track_ids = []
            for playlist_id in playlist_ids:
                if playlist_id == Type.POPULARS:
                    tracks = Lp().tracks.get_populars()
                elif playlist_id == Type.RECENTS:
                    tracks = Lp().tracks.get_recently_listened_to()
                elif playlist_id == Type.NEVER:
                    tracks = Lp().tracks.get_never_listened_to()
                elif playlist_id == Type.RANDOMS:
                    tracks = Lp().tracks.get_randoms()
                else:
                    tracks = Lp().playlists.get_track_ids(playlist_id)
                for track_id in tracks:
                    if track_id not in track_ids:
                        track_ids.append(track_id)
            return track_ids

        self.__stop_current_view()
        view = None
        if playlist_ids:
            from lollypop.view_playlists import PlaylistsView
            view = PlaylistsView(playlist_ids)
            loader = Loader(target=load, view=view)
            loader.start()
        else:
            from lollypop.view_playlists import PlaylistsManageView
            view = PlaylistsManageView(Type.NONE, [], [], False)
            view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __update_view_radios(self):
        """
            Update current view with radios view
        """
        from lollypop.view_radios import RadiosView
        self.__stop_current_view()
        view = RadiosView()
        view.populate()
        view.show()
        self.__stack.add(view)
        self.__stack.set_visible_child(view)
        self.__stack.clean_old_views(view)

    def __add_device(self, mount, show=False):
        """
            Add a device
            @param mount as Gio.Mount
            @param show as bool
        """
        if mount.get_volume() is None:
            return
        name = mount.get_name()
        uri = mount.get_default_location().get_uri()
        if uri is not None and (mount.can_eject() or uri.startswith('mtp')):
            self.__devices_index -= 1
            dev = Device()
            dev.id = self.__devices_index
            dev.name = name
            dev.uri = uri
            self.__devices[self.__devices_index] = dev
            if show:
                self.__list_one.add_value((dev.id, dev.name))

    def __remove_device(self, mount):
        """
            Remove volume from device list
            @param mount as Gio.Mount
        """
        uri = mount.get_default_location().get_uri()
        for dev in self.__devices.values():
            if dev.uri == uri:
                self.__list_one.remove_value(dev.id)
                child = self.__stack.get_child_by_name(uri)
                if child is not None:
                    child.destroy()
                del self.__devices[dev.id]
            break

    def __on_list_one_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        selected_ids = self.__list_one.selected_ids
        if not selected_ids:
            return
        self.__list_two.clear()
        if selected_ids[0] == Type.PLAYLISTS:
            self.__list_two.show()
            if not self.__list_two.will_be_selected():
                self.__update_view_playlists()
            self.__setup_list_playlists(False)
        elif Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES:
            self.__list_two.hide()
            if not self.__list_two.will_be_selected():
                self.__update_view_device(selected_ids[0])
        elif selected_ids[0] in [
                Type.POPULARS, Type.RECENTS, Type.RANDOMS, Type.CHARTS
        ]:
            self.__list_two.hide()
            self.__update_view_albums(selected_ids, [])
        elif selected_ids[0] == Type.RADIOS:
            self.__list_two.hide()
            self.__update_view_radios()
        elif selection_list.is_marked_as_artists():
            self.__list_two.hide()
            if selected_ids[0] == Type.ALL:
                self.__update_view_albums(selected_ids, [])
            elif selected_ids[0] == Type.COMPILATIONS:
                self.__update_view_albums([], selected_ids)
            else:
                self.__update_view_artists([], selected_ids)
        else:
            self.__setup_list_artists(self.__list_two, selected_ids, False)
            self.__list_two.show()
            if not self.__list_two.will_be_selected():
                self.__update_view_albums(selected_ids, [])

    def __on_list_populated(self, selection_list):
        """
            Add device to list one and update db
            @param selection list as SelectionList
        """
        for dev in self.__devices.values():
            self.__list_one.add_value((dev.id, dev.name))

    def __on_list_two_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        genre_ids = self.__list_one.selected_ids
        selected_ids = self.__list_two.selected_ids
        if not selected_ids or not genre_ids:
            return
        if genre_ids[0] == Type.PLAYLISTS:
            self.__update_view_playlists(selected_ids)
        elif selected_ids[0] == Type.COMPILATIONS:
            self.__update_view_albums(genre_ids, selected_ids)
        else:
            self.__update_view_artists(genre_ids, selected_ids)

    def __on_genre_updated(self, scanner, genre_id, add):
        """
            Add genre to genre list
            @param scanner as CollectionScanner
            @param genre id as int
            @param add as bool
        """
        if self.__show_genres:
            if add:
                genre_name = Lp().genres.get_name(genre_id)
                self.__list_one.add_value((genre_id, genre_name))
            else:
                genre_ids = Lp().genres.get_ids()
                if genre_id not in genre_ids:
                    self.__list_one.remove_value(genre_id)

    def __on_artist_updated(self, scanner, artist_id, add):
        """
            Add artist to artist list
            @param scanner as CollectionScanner
            @param artist id as int
            @param add as bool
        """
        artist_name = Lp().artists.get_name(artist_id)
        if self.__show_genres:
            l = self.__list_two
            artist_ids = Lp().artists.get_ids(self.__list_one.selected_ids)
        else:
            l = self.__list_one
            artist_ids = Lp().artists.get_ids()
        if add:
            if artist_id in artist_ids:
                l.add_value((artist_id, artist_name))
        else:
            if artist_id not in artist_ids:
                l.remove_value(artist_id)

    def __on_mount_added(self, vm, mount):
        """
            On volume mounter
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self.__add_device(mount, True)

    def __on_mount_removed(self, vm, mount):
        """
            On volume removed, clean selection list
            @param vm as Gio.VolumeMonitor
            @param mount as Gio.Mount
        """
        self.__remove_device(mount)
Exemple #39
0
class RadioPopover(Gtk.Popover):
    """
        Popover with radio logos from the web
    """

    def __init__(self, name, radios_manager):
        """
            Init Popover
            @param name as string
            @param radios_manager as RadiosManager
        """
        Gtk.Popover.__init__(self)
        self._name = name
        self._radios_manager = radios_manager
        self._start = 0
        self._orig_pixbufs = {}

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui')
        builder.connect_signals(self)

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect('child-activated', self._on_activate)
        self._view.set_max_children_per_line(100)
        self._view.set_property('row-spacing', 10)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._logo = builder.get_object('logo')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._name_entry = builder.get_object('name')
        self._uri_entry = builder.get_object('uri')
        self._btn_add_modify = builder.get_object('btn_add_modify')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._logo)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._widget)
        self.add(self._stack)

        if self._name == '':
            builder.get_object('btn_add_modify').set_label(_("Add"))
        else:
            builder.get_object('btn_add_modify').set_label(_("Modify"))
            builder.get_object('btn_delete').show()
            self._name_entry.set_text(self._name)
            uris = self._radios_manager.get_tracks(self._name)
            if len(uris) > 0:
                self._uri_entry.set_text(uris[0])

    def do_show(self):
        """
            Resize popover and set signals callback
        """
        Gtk.Popover.do_show(self)
        self._name_entry.grab_focus()
        Lp.window.enable_global_shorcuts(False)

    def do_hide(self):
        """
            Kill thread
        """
        self._thread = False
        Gtk.Popover.do_hide(self)
        Lp.window.enable_global_shorcuts(True)

#######################
# PRIVATE             #
#######################
    def _populate_threaded(self):
        """
            Populate view
        """
        self._thread = True
        t = Thread(target=self._populate)
        t.daemon = True
        t.start()

    def _populate(self):
        """
            Same as _populate_threaded()
            @thread safe
        """
        self._urls = Lp.art.get_google_arts(self._name+"+logo+radio",
                                            self._start)
        if self._urls:
            self._start += GOOGLE_INC
            self._add_pixbufs()
        else:
            GLib.idle_add(self._show_not_found)

    def _add_pixbufs(self):
        """
            Add urls to the view
        """
        if self._urls:
            url = self._urls.pop()
            stream = None
            try:
                f = Gio.File.new_for_uri(url)
                (status, data, tag) = f.load_contents()
                if status:
                    stream = Gio.MemoryInputStream.new_from_data(data, None)
            except:
                if self._thread:
                    self._add_pixbufs()
            if stream:
                GLib.idle_add(self._add_pixbuf, stream)
            if self._thread:
                self._add_pixbufs()
        elif self._start < GOOGLE_MAX:
            self._populate_threaded()

    def _show_not_found(self):
        """
            Show not found message if view empty
        """
        if len(self._view.get_children()) == 0:
            self._stack.set_visible_child(self._not_found)
            self._stack.clean_old_views(self._not_found)

    def _add_pixbuf(self, stream):
        """
            Add stream to the view
        """
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
                stream, ArtSize.MONSTER,
                ArtSize.MONSTER,
                True,
                None)
            image = Gtk.Image()
            self._orig_pixbufs[image] = pixbuf
            # Scale preserving aspect ratio
            width = pixbuf.get_width()
            height = pixbuf.get_height()
            if width > height:
                height = height*ArtSize.BIG*self.get_scale_factor()/width
                width = ArtSize.BIG*self.get_scale_factor()
            else:
                width = width*ArtSize.BIG*self.get_scale_factor()/height
                height = ArtSize.BIG*self.get_scale_factor()
            scaled_pixbuf = pixbuf.scale_simple(width,
                                                height,
                                                GdkPixbuf.InterpType.BILINEAR)
            del pixbuf
            surface = Gdk.cairo_surface_create_from_pixbuf(scaled_pixbuf,
                                                           0,
                                                           None)
            del scaled_pixbuf
            image.set_from_surface(surface)
            del surface
            image.show()
            self._view.add(image)
        except Exception as e:
            print(e)
            pass
        # Remove spinner if exist
        if self._spinner is not None:
            self._stack.set_visible_child(self._logo)
            self._stack.clean_old_views(self._logo)
            self._spinner = None

    def _on_btn_add_modify_clicked(self, widget):
        """
            Add/Modify a radio
            @param widget as Gtk.Widget
        """
        uri = self._uri_entry.get_text()
        new_name = self._name_entry.get_text()
        rename = self._name != '' and self._name != new_name

        if uri != '' and new_name != '':
            if rename:
                self._radios_manager.rename(new_name, self._name)
            else:
                self._radios_manager.add(new_name)
            self._radios_manager.add_track(new_name,
                                           uri.lstrip().rstrip())
            self._stack.remove(self._widget)
            self._stack.set_visible_child(self._spinner)
            self._name = new_name
            self._populate_threaded()
            self.set_size_request(700, 400)

    def _on_btn_delete_clicked(self, widget):
        """
            Delete a radio
            @param widget as Gtk.Widget
        """
        if self._name != '':
            cache = Art._RADIOS_PATH
            self._radios_manager.delete(self._name)
            Lp.art.clean_radio_cache(self._name)
            if os.path.exists(cache+"/%s.png" % self._name):
                os.remove(cache+"/%s.png" % self._name)

    def _on_activate(self, flowbox, child):
        """
            Use pixbuf as cover
            Reset cache and use player object to announce cover change
        """
        pixbuf = self._orig_pixbufs[child.get_child()]
        Lp.art.save_radio_logo(pixbuf, self._name)
        Lp.art.clean_radio_cache(self._name)
        Lp.art.announce_logo_update(self._name)
        self.hide()
        self._streams = {}

    def _on_entry_changed(self, entry):
        """
            Update modify/add button
            @param entry as Gtk.Entry
        """
        uri = self._uri_entry.get_text()
        name = self._name_entry.get_text()
        if name != '' and uri.find('://') != -1:
            self._btn_add_modify.set_sensitive(True)
        else:
            self._btn_add_modify.set_sensitive(False)
Exemple #40
0
class PopImages(Gtk.Popover):

    """
        Init Popover ui with a text entry and a scrolled treeview
    """
    def __init__(self, album_id):
        Gtk.Popover.__init__(self)
        self._album_id = album_id

        self._stack = ViewContainer(1000)
        self._stack.show()

        builder = Gtk.Builder()
        builder.add_from_resource(
                    '/org/gnome/Lollypop/PopImages.ui')

        self._view = Gtk.FlowBox()
        self._view.set_selection_mode(Gtk.SelectionMode.NONE)
        self._view.connect("child-activated", self._on_activate)
        self._view.show()

        builder.get_object('viewport').add(self._view)

        self._widget = builder.get_object('widget')
        self._spinner = builder.get_object('spinner')
        self._not_found = builder.get_object('notfound')
        self._stack.add(self._spinner)
        self._stack.add(self._not_found)
        self._stack.add(self._widget)
        self._stack.set_visible_child(self._spinner)
        self.add(self._stack)

    """
        Populate view
        @param searched words as string
    """
    def populate(self, string):
        self._thread = True
        start_new_thread(self._populate, (string,))

    """
        Resize popover and set signals callback
    """
    def do_show(self):
        self.set_size_request(700, 400)
        Gtk.Popover.do_show(self)

    """
        Kill thread
    """
    def do_hide(self):
        self._thread = False
        Gtk.Popover.do_hide(self)

#######################
# PRIVATE             #
#######################
    """
        Same as populate()
    """
    def _populate(self, string):
        self._urls = Objects.art.get_google_arts(string)
        if self._urls:
            self._add_pixbufs()
        else:
            GLib.idle_add(self._show_not_found)

    """
        Add urls to the view
    """
    def _add_pixbufs(self):
        if self._urls:
            url = self._urls.pop()
            stream = None
            try:
                response = urllib.request.urlopen(url)
                stream = Gio.MemoryInputStream.new_from_data(
                                                response.read(), None)
            except:
                if self._thread:
                    self._add_pixbufs()
            if stream:
                GLib.idle_add(self._add_pixbuf, stream)
            if self._thread:
                self._add_pixbufs()

    """
        Show not found message
    """
    def _show_not_found(self):
        self._stack.set_visible_child(self._not_found)
        self._stack.clean_old_views(self._not_found)

    """
        Add stream to the view
    """
    def _add_pixbuf(self, stream):
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
                                            stream, ArtSize.MONSTER,
                                            ArtSize.MONSTER,
                                            False,
                                            None)
            image = Gtk.Image()
            image.set_from_pixbuf(pixbuf.scale_simple(ArtSize.BIG,
                                                      ArtSize.BIG,
                                                      2))
            image.show()
            self._view.add(image)
        except Exception as e:
            print(e)
            pass
        # Remove spinner if exist
        if self._spinner is not None:
            self._stack.set_visible_child(self._widget)
            self._stack.clean_old_views(self._widget)
            self._spinner = None

    """
        Use pixbuf as cover
        Reset cache and use player object to announce cover change
    """
    def _on_activate(self, flowbox, child):
        pixbuf = child.get_child().get_pixbuf()
        Objects.art.save_art(pixbuf, self._album_id)
        Objects.art.clean_cache(self._album_id)
        Objects.player.announce_cover_update(self._album_id)
        self.hide()
        self._streams = {}
Exemple #41
0
class Container:
    def __init__(self):
        
        # Try to update db on start, will be done after list one poplating
        # finished
        self._need_to_update_db = True
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Navigation.DEVICES
        self._show_genres = Objects.settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        self._list_one_restore = Navigation.POPULARS
        self._list_two_restore = Navigation.NONE
        if Objects.settings.get_value('save-state'):
            self._restore_view_state()

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Objects.playlists.connect("playlists-changed",
                                  self.update_lists)

    """
        Update db at startup only if needed
        @param force as bool to force update (if possible)
    """
    def update_db(self, force=False):
        # Stop previous scan
        if self._scanner.is_locked():
            self._scanner.stop()
            GLib.timeout_add(250, self.update_db, force)
        # Something is using progress bar, do nothing
        elif not self._progress.is_visible():
            if force:
                Objects.tracks.remove_outside()
                self._list_one_restore = self._list_one.get_selected_id()
                self._list_two_restore = self._list_two.get_selected_id()
                self.update_lists(True)
                self._scanner.update(False)
            elif Objects.tracks.is_empty():
                self._scanner.update(False)
            elif Objects.settings.get_value('startup-scan'):
                self._scanner.update(True)

    """
        Save view state
    """
    def save_view_state(self):
        Objects.settings.set_value("list-one",
                                   GLib.Variant(
                                        'i',
                                        self._list_one.get_selected_id()))
        Objects.settings.set_value("list-two",
                                   GLib.Variant(
                                        'i',
                                        self._list_two.get_selected_id()))

    """
        Show playlist manager for object_id
        Current view stay present in ViewContainer
        @param object id as int
        @param genre id as int
        @param is_album as bool
    """
    def show_playlist_manager(self, object_id, genre_id, is_album):
        view = PlaylistManageView(object_id, genre_id, is_album,
                                  self._stack.get_allocated_width()/2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Show playlist editor for playlist
        Current view stay present in ViewContainer
        @param playlist name as str
    """
    def show_playlist_editor(self, playlist_name):
        view = PlaylistEditView(playlist_name,
                                self._stack.get_allocated_width()/2)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        start_new_thread(view.populate, ())

    """
        Update lists
        @param updater as GObject
    """
    def update_lists(self, updater=None):
        self._update_list_one(updater)
        self._update_list_two(updater)

    """
        Load external files
        @param files as [Gio.Files]
    """
    def load_external(self, files):
        self._need_to_update_db = False
        # We wait as selection list is threaded,
        # we don't want to insert item before populated
        # Same for locked db
        if self._list_one.is_populating() or\
           self._list_one.is_populating() or\
           self._scanner.is_locked():
            self._scanner.stop()
            GLib.timeout_add(250, self.load_external, files)
        else:
            self._scanner.add(files)

    """
        Get main widget
        @return Gtk.HPaned
    """
    def main_widget(self):
        return self._paned_main_list

    """
        Stop current view from processing
    """
    def stop_all(self):
        self._scanner.stop()
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    """
        Show/Hide genres
        @param bool
    """
    def show_genres(self, show):
        self._show_genres = show
        self._update_list_one(None)

    """
        Destroy current view
    """
    def destroy_current_view(self):
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    """
        Update current view
    """
    def update_view(self):
        view = self._stack.get_visible_child()
        if view:
            start_new_thread(view.update_covers, ())

############
# Private  #
############
    """
        Setup window main view:
            - genre list
            - artist list
            - main view as artist view or album view
    """
    def _setup_view(self):
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList()
        self._list_one.widget.show()
        self._list_two = SelectionList()
        self._list_one.connect('item-selected', self._on_list_one_selected)
        self._list_one.connect('populated', self._on_list_one_populated)
        self._list_two.connect('item-selected', self._on_list_two_selected)
        self._list_two.connect('populated', self._on_list_two_populated)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property('hexpand', True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        separator = Gtk.Separator()
        separator.show()
        self._paned_list_view.add1(self._list_two.widget)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one.widget)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
                        Objects.settings.get_value(
                                "paned-mainlist-width").get_int32())
        self._paned_list_view.set_position(
                        Objects.settings.get_value(
                                "paned-listview-width").get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    """
        Restore saved view
    """
    def _restore_view_state(self):
        position = Objects.settings.get_value('list-one').get_int32()
        if position != -1:
            self._list_one_restore = position
        else:
            self._list_one_restore = Navigation.POPULARS
        position = Objects.settings.get_value('list-two').get_int32()
        if position != -1:
            self._list_two_restore = position

    """
        Add genre to genre list
        @param scanner as CollectionScanner
        @param genre id as int
    """
    def _add_genre(self, scanner, genre_id):
        if self._show_genres:
            genre_name = Objects.genres.get_name(genre_id)
            self._list_one.add((genre_id, genre_name))

    """
        Add artist to artist list
        @param scanner as CollectionScanner
        @param artist id as int
        @param album id as int
    """
    def _add_artist(self, scanner, artist_id, album_id):
        artist_name = Objects.artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Objects.albums.get_genre_ids(album_id)
            genre_ids.append(Navigation.ALL)
            if self._list_one.get_selected_id() in genre_ids:
                self._list_two.add((artist_id, artist_name))
        else:
            self._list_one.add((artist_id, artist_name))

    """
        Run collection update if needed
        @return True if hard scan is running
    """
    def _setup_scanner(self):
        self._scanner = CollectionScanner(self._progress)
        self._scanner.connect("scan-finished", self._on_scan_finished)
        self._scanner.connect("genre-update", self._add_genre)
        self._scanner.connect("artist-update", self._add_artist)
        self._scanner.connect("added", self._play_track)

    """
        Update list one
        @param updater as GObject
    """
    def _update_list_one(self, updater):
        update = updater is not None
        # Do not update if updater is PlaylistsManager
        if not isinstance(updater, PlaylistsManager):
            if self._show_genres:
                self._setup_list_genres(self._list_one, update)
            else:
                self._setup_list_artists(self._list_one,
                                         Navigation.ALL,
                                         update)

    """
        Update list two
        @param updater as GObject
    """
    def _update_list_two(self, updater):
        update = updater is not None
        object_id = self._list_one.get_selected_id()

        if object_id == Navigation.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (update,))
        elif self._show_genres and object_id != Navigation.NONE:
            self._setup_list_artists(self._list_two, object_id, update)

    """
        Return list one headers
    """
    def _get_headers(self):
        items = []
        items.append((Navigation.POPULARS, _("Popular albums")))
        items.append((Navigation.RECENTS, _("Recent albums")))
        items.append((Navigation.PLAYLISTS, _("Playlists")))
        if self._show_genres:
            items.append((Navigation.ALL, _("All artists")))
        else:
            items.append((Navigation.ALL, _("All albums")))
        return items

    """
        Setup list for genres
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """
    def _setup_list_genres(self, selection_list, update):
        sql = Objects.db.get_cursor()
        selection_list.mark_as_artists(False)
        items = self._get_headers() + Objects.genres.get(sql)
        if update:
            selection_list.update(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Hide list two base on current artist list
    """
    def _pre_setup_list_artists(self, selection_list):
        if selection_list == self._list_one:
            if self._list_two.widget.is_visible():
                self._list_two.widget.hide()
            self._list_two_restore = Navigation.NONE

    """
        Setup list for artists
        @param list as SelectionList
        @param update as bool, if True, just update entries
        @thread safe
    """
    def _setup_list_artists(self, selection_list, genre_id, update):
        GLib.idle_add(self._pre_setup_list_artists, selection_list)
        sql = Objects.db.get_cursor()
        items = []
        selection_list.mark_as_artists(True)
        if selection_list == self._list_one:
            items = self._get_headers()
        if Objects.albums.get_compilations(genre_id, sql):
            items.append((Navigation.COMPILATIONS, _("Compilations")))

        items += Objects.artists.get(genre_id, sql)

        if update:
            selection_list.update(items)
        else:
            selection_list.populate(items)
        sql.close()

    """
        Setup list for playlists
        @param update as bool
        @thread safe
    """
    def _setup_list_playlists(self, update):
        playlists = Objects.playlists.get()
        if update:
            self._list_two.update(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)
            GLib.idle_add(self._update_view_playlists, None)

    """
        Update current view with device view,
        Use existing view if available
        @param object id as int
    """
    def _update_view_device(self, object_id):
        device = self._devices[object_id]

        if device and device.view:
            view = device.view
        else:
            view = DeviceView(device, self._progress,
                              self._stack.get_allocated_width()/2)
            device.view = view
            view.show()
            start_new_thread(view.populate, ())
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with artists view
        @param object id as int
        @param genre id as int
    """
    def _update_view_artists(self, object_id, genre_id):
        view = ArtistView(object_id, True)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, (genre_id,))
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with albums view
        @param object id as int
        @param genre id as int
    """
    def _update_view_albums(self, object_id, genre_id):
        view = AlbumView(object_id)
        self._stack.add(view)
        view.show()
        start_new_thread(view.populate, (genre_id,))
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    """
        Update current view with playlist view
        @param playlist id as int
    """
    def _update_view_playlists(self, playlist_id):
        view = None
        if playlist_id is not None:
            for (p_id, p_str) in Objects.playlists.get():
                if p_id == playlist_id:
                    view = PlaylistView(p_str, self._stack)
                    break
        else:
            view = PlaylistManageView(-1, None, False,
                                      self._stack.get_allocated_width()/2)
        if view:
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            start_new_thread(view.populate, ())
            self._stack.clean_old_views(view)

    """
        Add volume to device list
        @param volume as Gio.Volume
    """
    def _add_device(self, volume):
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return
        path = root.get_path()
        if path and path.find('mtp:') != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = volume.get_name()
            dev.path = path
            self._devices[self._devices_index] = dev
            self._list_one.add_device(dev.name, dev.id)

    """
        Remove volume from device list
        @param volume as Gio.Volume
    """
    def _remove_device(self, volume):
        for dev in self._devices.values():
            if not os.path.exists(dev.path):
                self._list_one.remove(dev.id)
                device = self._devices[dev.id]
                if device.view:
                    device.view.destroy()
                del self._devices[dev.id]
            break

    """
        Update view based on selected object
        @param list as SelectionList
        @param object id as int
    """
    def _on_list_one_selected(self, selection_list, object_id):
        if object_id == Navigation.PLAYLISTS:
            start_new_thread(self._setup_list_playlists, (False,))
            self._list_two.widget.show()
        elif object_id < Navigation.DEVICES:
            self._list_two.widget.hide()
            self._update_view_device(object_id)
        elif object_id in [Navigation.POPULARS, Navigation.RECENTS]:
            self._list_two.widget.hide()
            self._update_view_albums(object_id, None)
        elif selection_list.is_marked_as_artists():
            self._list_two.widget.hide()
            if object_id == Navigation.ALL or\
               object_id == Navigation.COMPILATIONS:
                self._update_view_albums(object_id, None)
            else:
                self._update_view_artists(object_id, None)
        else:
            start_new_thread(self._setup_list_artists,
                             (self._list_two, object_id, False))
            self._list_two.widget.show()
            if self._list_two_restore == Navigation.NONE:
                self._update_view_albums(object_id, None)

    """
        Restore previous state
        @param selection list as SelectionList
    """
    def _on_list_one_populated(self, selection_list):
        for dev in self._devices.values():
            self._list_one.add_device(dev.name, dev.id)
        if self._list_one_restore is not None:
            self._list_one.select_id(self._list_one_restore)
            self._list_one_restore = None
        if self._need_to_update_db:
            self._need_to_update_db = False
            self.update_db()

    """
        Update view based on selected object
        @param list as SelectionList
        @param object id as int
    """
    def _on_list_two_selected(self, selection_list, object_id):
        selected_id = self._list_one.get_selected_id()
        if selected_id == Navigation.PLAYLISTS:
            self._update_view_playlists(object_id)
        elif object_id == Navigation.COMPILATIONS:
            self._update_view_albums(object_id, selected_id)
        else:
            self._update_view_artists(object_id, selected_id)

    """
        Restore previous state
        @param selection list as SelectionList
    """
    def _on_list_two_populated(self, selection_list):
        if self._list_two_restore != Navigation.NONE:
            self._list_two.select_id(self._list_two_restore)
            self._list_two_restore = Navigation.NONE

    """
        Play tracks as user playlist
        @param scanner as collection scanner
        @param track id as int
        @param play as bool
    """
    def _play_track(self, scanner, track_id, play):
        tracks = [track_id]
        if play:
            Objects.player.clear_user_playlist()
            if not Objects.player.is_party():
                Objects.player.set_user_playlist(tracks, track_id)
            Objects.player.load(track_id)
        elif not Objects.player.is_party():
            Objects.player.add_to_user_playlist(track_id)

    """
        Mark force scan as False, update lists
        @param scanner as CollectionScanner
    """
    def _on_scan_finished(self, scanner):
        if self._list_one.is_populating() or self._list_two.is_populating():
            GLib.timeout_add(500, self._on_scan_finished, scanner)
        else:
            self.update_lists(scanner)

    """
        On volume mounter
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """
    def _on_mount_added(self, vm, mnt):
        self._add_device(mnt.get_volume())

    """
        On volume removed, clean selection list
        @param vm as Gio.VolumeMonitor
        @param mnt as Gio.Mount
    """
    def _on_mount_removed(self, vm, mnt):
        self._remove_device(mnt.get_volume())
Exemple #42
0
class Container:
    """
        Container for main view
    """
    def __init__(self):
        """
            Init container
        """
        self._pulse_timeout = None
        # Index will start at -VOLUMES
        self._devices = {}
        self._devices_index = Type.DEVICES
        self._show_genres = Lp().settings.get_value('show-genres')
        self._stack = ViewContainer(500)
        self._stack.show()

        self._setup_view()
        self._setup_scanner()

        (list_one_ids, list_two_ids) = self._get_saved_view_state()
        if list_one_ids and list_one_ids[0] != Type.NONE:
            self._list_one.select_ids(list_one_ids)
        if list_two_ids and list_two_ids[0] != Type.NONE:
            self._list_two.select_ids(list_two_ids)

        # Volume manager
        self._vm = Gio.VolumeMonitor.get()
        self._vm.connect('mount-added', self._on_mount_added)
        self._vm.connect('mount-removed', self._on_mount_removed)

        Lp().playlists.connect('playlists-changed', self._update_playlists)

    def update_db(self):
        """
            Update db at startup only if needed
        """
        # Stop previous scan
        if Lp().scanner.is_locked():
            Lp().scanner.stop()
            GLib.timeout_add(250, self.update_db)
        else:
            # Something (device manager) is using progress bar
            progress = None
            if not self._progress.is_visible():
                progress = self._progress
            Lp().scanner.update(progress)

    def get_genre_id(self):
        """
            Return current selected genre
            @return genre id as int
        """
        if self._show_genres:
            return self._list_one.get_selected_id()
        else:
            return None

    def init_list_one(self):
        """
            Init list one
        """
        self._update_list_one(None)

    def save_view_state(self):
        """
            Save view state
        """
        Lp().settings.set_value(
            "list-one-ids",
            GLib.Variant('ai', self._list_one.get_selected_ids()))
        Lp().settings.set_value(
            "list-two-ids",
            GLib.Variant('ai', self._list_two.get_selected_ids()))

    def show_playlist_manager(self, object_id, genre_id, is_album):
        """
            Show playlist manager for object_id
            Current view stay present in ViewContainer
            @param object id as int
            @param genre id as int
            @param is_album as bool
        """
        view = PlaylistsManageView(object_id, genre_id, is_album)
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)

    def show_playlist_editor(self, playlist_id):
        """
            Show playlist editor for playlist
            Current view stay present in ViewContainer
            @param playlist id as int
            @param playlist name as str
        """
        view = PlaylistEditView(playlist_id)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        view.populate()

    def main_widget(self):
        """
            Get main widget
            @return Gtk.HPaned
        """
        return self._paned_main_list

    def stop_all(self):
        """
            Stop current view from processing
        """
        view = self._stack.get_visible_child()
        if view is not None:
            self._stack.clean_old_views(None)

    def show_genres(self, show):
        """
            Show/Hide genres
            @param bool
        """
        self._show_genres = show
        self._list_one.clear()
        self._update_list_one(None)

    def destroy_current_view(self):
        """
            Destroy current view
        """
        view = self._stack.get_visible_child()
        for child in self._stack.get_children():
            if child != view:
                self._stack.set_visible_child(child)
                self._stack.clean_old_views(child)
                break

    def update_view(self):
        """
            Update current view
        """
        view = self._stack.get_visible_child()
        if view:
            view.update_children()

    def reload_view(self):
        """
            Reload current view
        """
        if self._list_two.is_visible():
            values = self._list_two.get_selected_ids()
            self._list_two.select_ids(values)
        else:
            values = self._list_one.get_selected_ids()
            self._list_one.select_ids(values)

    def pulse(self, pulse):
        """
            Make progress bar visible/pulse if pulse is True
            @param pulse as bool
        """
        if pulse:
            self._progress.show()
            if self._pulse_timeout is None:
                self._pulse_timeout = GLib.timeout_add(500, self._pulse)
        else:
            if self._pulse_timeout is not None:
                GLib.source_remove(self._pulse_timeout)
                self._pulse_timeout = None
            self._progress.hide()

    def on_scan_finished(self, scanner):
        """
            Mark force scan as False, update lists
            @param scanner as CollectionScanner
        """
        self._update_lists(scanner)

    def add_fake_phone(self):
        """
            Emulate an Android Phone
        """
        self._devices_index -= 1
        dev = Device()
        dev.id = self._devices_index
        dev.name = "Android phone"
        dev.uri = "file:///tmp/android/"
        d = Gio.File.new_for_uri(dev.uri + "Internal Memory")
        if not d.query_exists(None):
            d.make_directory_with_parents(None)
        d = Gio.File.new_for_uri(dev.uri + "SD Card")
        if not d.query_exists(None):
            d.make_directory_with_parents(None)
        self._devices[self._devices_index] = dev

############
# Private  #
############

    def _pulse(self):
        """
            Make progress bar pulse while visible
            @param pulse as bool
        """
        if self._progress.is_visible() and not Lp().scanner.is_locked():
            self._progress.pulse()
            return True
        else:
            self._progress.set_fraction(0.0)
            return False

    def _setup_view(self):
        """
            Setup window main view:
                - genre list
                - artist list
                - main view as artist view or album view
        """
        self._paned_main_list = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self._paned_list_view = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        vgrid = Gtk.Grid()
        vgrid.set_orientation(Gtk.Orientation.VERTICAL)

        self._list_one = SelectionList()
        self._list_one.show()
        self._list_two = SelectionList()
        self._list_one.connect('item-selected', self._on_list_one_selected)
        self._list_one.connect('populated', self._on_list_populated)
        self._list_two.connect('item-selected', self._on_list_two_selected)

        self._progress = Gtk.ProgressBar()
        self._progress.set_property('hexpand', True)

        vgrid.add(self._stack)
        vgrid.add(self._progress)
        vgrid.show()

        self._paned_list_view.add1(self._list_two)
        self._paned_list_view.add2(vgrid)
        self._paned_main_list.add1(self._list_one)
        self._paned_main_list.add2(self._paned_list_view)
        self._paned_main_list.set_position(
            Lp().settings.get_value('paned-mainlist-width').get_int32())
        self._paned_list_view.set_position(
            Lp().settings.get_value('paned-listview-width').get_int32())
        self._paned_main_list.show()
        self._paned_list_view.show()

    def _get_saved_view_state(self):
        """
            Get save view state
            @return (list one id, list two id)
        """
        list_one_ids = [Type.POPULARS]
        list_two_ids = [Type.NONE]
        if Lp().settings.get_value('save-state'):
            list_one_ids = []
            list_two_ids = []
            ids = Lp().settings.get_value('list-one-ids')
            for i in ids:
                if isinstance(i, int):
                    list_one_ids.append(i)
            ids = Lp().settings.get_value('list-two-ids')
            for i in ids:
                if isinstance(i, int):
                    list_two_ids.append(i)
        return (list_one_ids, list_two_ids)

    def _add_genre(self, scanner, genre_id):
        """
            Add genre to genre list
            @param scanner as CollectionScanner
            @param genre id as int
        """
        if self._show_genres:
            genre_name = Lp().genres.get_name(genre_id)
            self._list_one.add_value((genre_id, genre_name))

    def _add_artist(self, scanner, artist_id, album_id):
        """
            Add artist to artist list
            @param scanner as CollectionScanner
            @param artist id as int
            @param album id as int
        """
        artist_name = Lp().artists.get_name(artist_id)
        if self._show_genres:
            genre_ids = Lp().albums.get_genre_ids(album_id)
            genre_ids.append(Type.ALL)
            for i in self._list_one.get_selected_ids():
                if i in genre_ids:
                    self._list_two.add_value((artist_id, artist_name))
        else:
            self._list_one.add_value((artist_id, artist_name))

    def _setup_scanner(self):
        """
            Run collection update if needed
            @return True if hard scan is running
        """
        Lp().scanner.connect('scan-finished', self.on_scan_finished)
        Lp().scanner.connect('genre-update', self._add_genre)
        Lp().scanner.connect('artist-update', self._add_artist)

    def _update_playlists(self, playlists, playlist_id):
        """
            Update playlists in second list
            @param playlists as Playlists
            @param playlist_id as int
        """
        ids = self._list_one.get_selected_ids()
        if ids and ids[0] == Type.PLAYLISTS:
            if Lp().playlists.exists(playlist_id):
                self._list_two.update_value(
                    playlist_id,
                    Lp().playlists.get_name(playlist_id))
            else:
                self._list_two.remove(playlist_id)

    def _update_lists(self, updater=None):
        """
            Update lists
            @param updater as GObject
        """
        self._update_list_one(updater)
        self._update_list_two(updater)

    def _update_list_one(self, updater):
        """
            Update list one
            @param updater as GObject
        """
        update = updater is not None
        if self._show_genres:
            self._setup_list_genres(self._list_one, update)
        else:
            self._setup_list_artists(self._list_one, [Type.ALL], update)

    def _update_list_two(self, updater):
        """
            Update list two
            @param updater as GObject
        """
        update = updater is not None
        ids = self._list_one.get_selected_ids()
        if ids and ids[0] == Type.PLAYLISTS:
            self._setup_list_playlists(update)
        elif self._show_genres and ids:
            self._setup_list_artists(self._list_two, ids, update)

    def _get_headers(self):
        """
            Return list one headers
        """
        items = []
        items.append((Type.POPULARS, _("Popular albums")))
        items.append((Type.RECENTS, _("Recently added albums")))
        items.append((Type.RANDOMS, _("Random albums")))
        items.append((Type.PLAYLISTS, _("Playlists")))
        items.append((Type.RADIOS, _("Radios")))
        if self._show_genres:
            items.append((Type.ALL, _("All artists")))
        else:
            items.append((Type.ALL, _("All albums")))
        return items

    def _setup_list_genres(self, selection_list, update):
        """
            Setup list for genres
            @param list as SelectionList
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            genres = Lp().genres.get()
            return genres

        def setup(genres):
            items = self._get_headers()
            items.append((Type.SEPARATOR, ''))
            items += genres
            selection_list.mark_as_artists(False)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        loader = Loader(target=load, view=selection_list, on_finished=setup)
        loader.start()

    def _setup_list_artists(self, selection_list, genre_ids, update):
        """
            Setup list for artists
            @param list as SelectionList
            @param genre ids as [int]
            @param update as bool, if True, just update entries
            @thread safe
        """
        def load():
            artists = Lp().artists.get(genre_ids)
            compilations = Lp().albums.get_compilations(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            if selection_list == self._list_one:
                items = self._get_headers()
                items.append((Type.SEPARATOR, ''))
            else:
                items = []
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
            items += artists
            selection_list.mark_as_artists(True)
            if update:
                selection_list.update_values(items)
            else:
                selection_list.populate(items)

        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.hide()
            self._list_two_restore = Type.NONE
        loader = Loader(target=load,
                        view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def _setup_list_playlists(self, update):
        """
            Setup list for playlists
            @param update as bool
            @thread safe
        """
        playlists = [(Type.LOVED, Lp().playlists._LOVED)]
        playlists.append((Type.POPULARS, _("Popular tracks")))
        playlists.append((Type.RECENTS, _("Recently played")))
        playlists.append((Type.NEVER, _("Never played")))
        playlists.append((Type.RANDOMS, _("Random tracks")))
        playlists.append((Type.MPD, _("Network control")))
        playlists.append((Type.SEPARATOR, ''))
        playlists += Lp().playlists.get()
        if update:
            self._list_two.update_values(playlists)
        else:
            self._list_two.mark_as_artists(False)
            self._list_two.populate(playlists)

    def _update_view_device(self, device_id):
        """
            Update current view with device view,
            Use existing view if available
            @param device id as int
        """
        device = self._devices[device_id]
        child = self._stack.get_child_by_name(device.uri)
        if child is None:
            if DeviceView.get_files(device.uri):
                child = DeviceView(device, self._progress)
                self._stack.add_named(child, device.uri)
            else:
                child = DeviceLocked()
                self._stack.add(child)
            child.show()
        child.populate()
        self._stack.set_visible_child(child)
        self._stack.clean_old_views(child)

    def _update_view_artists(self, artist_ids, genre_ids):
        """
            Update current view with artists view
            @param artist id as int
            @param genre id as int
        """
        def load():
            if artist_ids and artist_ids[0] == Type.COMPILATIONS:
                albums = Lp().albums.get_compilations(genre_ids)
            elif genre_ids and genre_ids[0] == Type.ALL:
                albums = Lp().albums.get_ids(artist_ids, [])
            else:
                albums = Lp().albums.get_ids(artist_ids, genre_ids)
            return albums

        view = ArtistView(artist_ids, genre_ids)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_albums(self, genre_ids, is_compilation=False):
        """
            Update current view with albums view
            @param genre ids as [int]
            @param is compilation as bool
        """
        def load():
            albums = []
            if genre_ids and genre_ids[0] == Type.ALL:
                if is_compilation:
                    albums = Lp().albums.get_compilations()
                else:
                    if Lp().settings.get_value('show-compilations'):
                        albums = Lp().albums.get_compilations()
                    albums += Lp().albums.get_ids()
            elif genre_ids and genre_ids[0] == Type.POPULARS:
                albums = Lp().albums.get_populars()
            elif genre_ids and genre_ids[0] == Type.RECENTS:
                albums = Lp().albums.get_recents()
            elif genre_ids and genre_ids[0] == Type.RANDOMS:
                albums = Lp().albums.get_randoms()
            elif is_compilation:
                albums = Lp().albums.get_compilations(genre_ids)
            else:
                if Lp().settings.get_value('show-compilations'):
                    albums = Lp().albums.get_compilations(genre_ids)
                albums += Lp().albums.get_ids([], genre_ids)
            return albums

        view = AlbumsView(genre_ids, is_compilation)
        loader = Loader(target=load, view=view)
        loader.start()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _update_view_playlists(self, playlist_id):
        """
            Update current view with playlist view
            @param playlist id as int
        """
        def load():
            if playlist_id == Lp().player.get_user_playlist_id():
                tracks = [t.id for t in Lp().player.get_user_playlist()]
            elif playlist_id == Type.POPULARS:
                tracks = Lp().tracks.get_populars()
            elif playlist_id == Type.RECENTS:
                tracks = Lp().tracks.get_recently_listened_to()
            elif playlist_id == Type.NEVER:
                tracks = Lp().tracks.get_never_listened_to()
            elif playlist_id == Type.RANDOMS:
                tracks = Lp().tracks.get_randoms()
            else:
                tracks = Lp().playlists.get_tracks_ids(playlist_id)
            return tracks

        view = None
        if playlist_id is not None:
            view = PlaylistView(playlist_id)
        else:
            view = PlaylistsManageView(Type.NONE, None, False)
        if view:
            # Management or user playlist
            if playlist_id is None:
                view.populate()
            else:
                loader = Loader(target=load, view=view)
                loader.start()
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    def _update_view_radios(self):
        """
            Update current view with radios view
        """
        view = RadiosView()
        view.populate()
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self._stack.clean_old_views(view)

    def _add_device(self, volume):
        """
            Add volume to device list
            @param volume as Gio.Volume
        """
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        # Just to be sure
        if uri is not None and len(uri) > 1 and uri[-1:] != '/':
            uri += '/'
        if uri is not None and uri.find('mtp:') != -1:
            self._devices_index -= 1
            dev = Device()
            dev.id = self._devices_index
            dev.name = volume.get_name()
            dev.uri = uri
            self._devices[self._devices_index] = dev
            self._list_one.add_value((dev.id, dev.name))

    def _remove_device(self, volume):
        """
            Remove volume from device list
            @param volume as Gio.Volume
        """
        if volume is None:
            return
        root = volume.get_activation_root()
        if root is None:
            return

        uri = root.get_uri()
        for dev in self._devices.values():
            if dev.uri == uri:
                self._list_one.remove(dev.id)
                child = self._stack.get_child_by_name(uri)
                if child is not None:
                    child.destroy()
                del self._devices[dev.id]
            break

    def _on_list_one_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        selected_ids = self._list_one.get_selected_ids()
        if not selected_ids:
            return
        if selected_ids[0] == Type.PLAYLISTS:
            self._list_two.clear()
            self._list_two.set_mode(Gtk.SelectionMode.SINGLE)
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_playlists(None)
            self._setup_list_playlists(False)
        elif Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES:
            self._list_two.hide()
            if not self._list_two.will_be_selected():
                self._update_view_device(selected_ids[0])
        elif selected_ids[0] in [Type.POPULARS, Type.RECENTS, Type.RANDOMS]:
            self._list_two.hide()
            self._update_view_albums(selected_ids)
        elif selected_ids[0] == Type.RADIOS:
            self._list_two.hide()
            self._update_view_radios()
        elif selection_list.is_marked_as_artists():
            self._list_two.hide()
            if selected_ids[0] == Type.ALL:
                self._update_view_albums(selected_ids)
            elif selected_ids[0] == Type.COMPILATIONS:
                self._update_view_albums([], True)
            else:
                self._update_view_artists(selected_ids, [])
        else:
            self._list_two.clear()
            self._list_two.set_mode(Gtk.SelectionMode.MULTIPLE)
            self._setup_list_artists(self._list_two, selected_ids, False)
            self._list_two.show()
            if not self._list_two.will_be_selected():
                self._update_view_albums(selected_ids, False)

    def _on_list_populated(self, selection_list):
        """
            Add device to list one and update db
            @param selection list as SelectionList
        """
        for dev in self._devices.values():
            self._list_one.add_value((dev.id, dev.name))

    def _on_list_two_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        genre_ids = self._list_one.get_selected_ids()
        selected_ids = self._list_two.get_selected_ids()
        if not selected_ids or not genre_ids:
            return
        if genre_ids[0] == Type.PLAYLISTS:
            self._update_view_playlists(selected_ids[0])
        elif selected_ids[0] == Type.COMPILATIONS:
            self._update_view_albums(genre_ids, True)
        else:
            self._update_view_artists(selected_ids, genre_ids)

    def _on_mount_added(self, vm, mnt):
        """
            On volume mounter
            @param vm as Gio.VolumeMonitor
            @param mnt as Gio.Mount
        """
        self._add_device(mnt.get_volume())

    def _on_mount_removed(self, vm, mnt):
        """
            On volume removed, clean selection list
            @param vm as Gio.VolumeMonitor
            @param mnt as Gio.Mount
        """
        self._remove_device(mnt.get_volume())
Exemple #43
0
class AlbumView(View):
    """
        Init album view ui with a scrolled flow box and a scrolled context view
        @param navigation id as int
    """
    def __init__(self, navigation_id):
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = navigation_id
        self._albumsongs = None
        self._context_widget = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect("child-activated", self._on_album_activated)
        self._albumbox.set_max_children_per_line(100)
        self._albumbox.show()

        self._viewport.set_property("valign", Gtk.Align.START)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow)
        self._paned.pack2(self._context, True, False)
        height = Objects.settings.get_value(
                                         'paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Objects.window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    """
        Populate albums, thread safe
        @param navigation id as int
    """
    def populate(self, navigation_id):
        sql = Objects.db.get_cursor()
        if self._genre_id == Navigation.ALL:
            albums = Objects.albums.get_ids(None, None, sql)
        elif self._genre_id == Navigation.POPULARS:
            albums = Objects.albums.get_populars(sql)
        elif self._genre_id == Navigation.RECENTS:
            albums = Objects.albums.get_recents(sql)
        elif self._genre_id == Navigation.COMPILATIONS:
            albums = Objects.albums.get_compilations(navigation_id,
                                                     sql)
        else:
            albums = Objects.albums.get_ids(None, self._genre_id, sql)
        GLib.idle_add(self._add_albums, albums)
        sql.close()

#######################
# PRIVATE             #
#######################
    """
        Return view children
        @return [AlbumWidget]
    """
    def _get_children(self):
        children = []
        for child in self._albumbox.get_children():
            for widget in child.get_children():
                children.append(widget)
        return children

    """
        populate context view
        @param album id as int
    """
    def _populate_context(self, album_id):
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumDetailedWidget(album_id,
                                                   self._genre_id,
                                                   True,
                                                   True,
                                                   size_group)
        start_new_thread(self._context_widget.populate, ())
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    """
        Save paned position
        @param paned as Gtk.Paned
        @param param as Gtk.Param
    """
    def _on_position_notify(self, paned, param):
        Objects.settings.set_value(
                            'paned-context-height',
                            GLib.Variant('i', paned.get_position()))
        return False

    """
        Show Context view for activated album
        @param flowbox, children
    """
    def _on_album_activated(self, flowbox, child):
        if self._context_album_id == child.get_child().get_id():
            if Objects.settings.get_value('auto-play'):
                Objects.player.play_album(self._context_album_id)
            else:
                self._context_album_id = None
                self._context.hide()
        else:
            self._context_album_id = child.get_child().get_id()
            self._populate_context(self._context_album_id)
            self._context.show()

    """
        Pop an album and add it to the view,
        repeat operation until album list is empty
        @param [album ids as int]
    """
    def _add_albums(self, albums):
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.show()
            self._albumbox.insert(widget, -1)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False
Exemple #44
0
class AlbumsPopover(Gtk.Popover):
    """
        Init Popover ui with a text entry and a scrolled treeview
    """
    def __init__(self):
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_id = None
        self.add(self._stack)

        Lp.player.connect("current-changed", self._update_content)

    """
        Run _populate in a thread
    """
    def populate(self):
        if Lp.player.current_track.aartist_id == Type.COMPILATIONS:
            new_id = Lp.player.current_track.album_id
        else:
            new_id = Lp.player.current_track.aartist_id
        if self._on_screen_id != new_id:
            self._on_screen_id = new_id
            view = PopArtistView(Lp.player.current_track.aartist_id)
            view.show()
            start_new_thread(view.populate, ())
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    """
        Resize popover
    """
    def do_show(self):
        size_setting = Lp.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and\
           isinstance(size_setting[1], int):
            self.set_size_request(size_setting[0]*0.65, size_setting[1]*0.8)
        else:
            self.set_size_request(600, 600)
        Gtk.Popover.do_show(self)

    """
        Remove view
    """
    def do_hide(self):
        Gtk.Popover.do_hide(self)
        child = self._stack.get_visible_child()
        if child is not None:
            child.stop()
        self._on_screen_id = None
        self._stack.clean_old_views(None)

#######################
# PRIVATE             #
#######################
    """
        Update the content view
        @param player as Player
        @param track id as int
    """
    def _update_content(self, player):
        if self.is_visible():
            self.populate()
Exemple #45
0
class AlbumView(View):
    """
        Init album view ui with a scrolled flow box and a scrolled context view
        @param navigation id as int
    """
    def __init__(self, navigation_id):
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = navigation_id
        self._albumsongs = None
        self._context_widget = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect("child-activated", self._on_album_activated)
        self._albumbox.set_max_children_per_line(100)
        self._albumbox.show()

        self._viewport.set_property("valign", Gtk.Align.START)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow)
        self._paned.pack2(self._context, True, False)
        height = Objects.settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Objects.window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    """
        Populate albums, thread safe
        @param navigation id as int
    """

    def populate(self, navigation_id):
        sql = Objects.db.get_cursor()
        if self._genre_id == Navigation.ALL:
            albums = Objects.albums.get_ids(None, None, sql)
        elif self._genre_id == Navigation.POPULARS:
            albums = Objects.albums.get_populars(sql)
        elif self._genre_id == Navigation.RECENTS:
            albums = Objects.albums.get_recents(sql)
        elif self._genre_id == Navigation.COMPILATIONS:
            albums = Objects.albums.get_compilations(navigation_id, sql)
        else:
            albums = Objects.albums.get_ids(None, self._genre_id, sql)
        GLib.idle_add(self._add_albums, albums)
        sql.close()

#######################
# PRIVATE             #
#######################

    """
        Return view children
        @return [AlbumWidget]
    """
    def _get_children(self):
        children = []
        for child in self._albumbox.get_children():
            for widget in child.get_children():
                children.append(widget)
        return children

    """
        populate context view
        @param album id as int
    """

    def _populate_context(self, album_id):
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumDetailedWidget(album_id, self._genre_id,
                                                   True, True, size_group)
        start_new_thread(self._context_widget.populate, ())
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    """
        Save paned position
        @param paned as Gtk.Paned
        @param param as Gtk.Param
    """

    def _on_position_notify(self, paned, param):
        Objects.settings.set_value('paned-context-height',
                                   GLib.Variant('i', paned.get_position()))
        return False

    """
        Show Context view for activated album
        @param flowbox, children
    """

    def _on_album_activated(self, flowbox, child):
        if self._context_album_id == child.get_child().get_id():
            if Objects.settings.get_value('auto-play'):
                Objects.player.play_album(self._context_album_id)
            else:
                self._context_album_id = None
                self._context.hide()
        else:
            self._context_album_id = child.get_child().get_id()
            self._populate_context(self._context_album_id)
            self._context.show()

    """
        Pop an album and add it to the view,
        repeat operation until album list is empty
        @param [album ids as int]
    """

    def _add_albums(self, albums):
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.show()
            self._albumbox.insert(widget, -1)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False
Exemple #46
0
class AlbumsView(View):
    """
        Show albums in a box
    """

    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp().settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp().window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self, albums):
        """
            Populate albums
            @param is compilation as bool
        """
        self._add_albums(albums)

#######################
# PRIVATE             #
#######################
    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumContextWidget(album_id,
                                                  self._genre_id,
                                                  True,
                                                  size_group)
        self._context_widget.populate()
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    def _add_albums(self, albums):
        """
            Pop an album and add it to the view,
            repeat operation until album list is empty
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.show()
            self._albumbox.insert(widget, -1)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        Lp().settings.set_value('paned-context-height',
                                GLib.Variant('i', paned.get_position()))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @param child as Gtk.FlowboxChild
        """
        album_widget = child.get_child()
        if self._press_rect is None:
            if self._context_album_id == album_widget.get_id():
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            else:
                if Lp().settings.get_value('auto-play'):
                    album = Album(album_widget.get_id())
                    track = Track(album.tracks_ids[0])
                    Lp().player.load(track)
                    Lp().player.set_albums(track.id, None,
                                           self._genre_id)
                else:
                    self._context_album_id = album_widget.get_id()
                    self._populate_context(self._context_album_id)
                    self._context.show()
        else:
            if self._context_album_id is not None:
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            popover = AlbumPopoverWidget(album_widget.get_id(),
                                         self._genre_id)
            popover.set_relative_to(album_widget)
            popover.set_pointing_to(self._press_rect)
            self._context_widget = popover.get_widget()
            popover.connect('destroy', self._on_popover_destroyed)
            popover.show()

    def _on_popover_destroyed(self, popover):
        """
            Remove from context
            @param popover as AlbumPopoverWidget
        """
        self._context_widget = None

    def _on_button_press(self, flowbox, event):
        """
            Store pressed button
            @param flowbox as Gtk.Flowbox
            @param event as Gdk.EventButton
        """
        if event.button == 1:
            self._press_rect = None
        else:
            self._press_rect = Gdk.Rectangle()
            self._press_rect.x = event.x
            self._press_rect.y = event.y
            self._press_rect.width = self._press_rect.height = 1
            event.button = 1
Exemple #47
0
class AlbumsView(LazyLoadingView):
    """
        Show albums in a box
    """
    def __init__(self, genre_ids, artist_ids):
        """
            Init album view
            @param genre ids as [int]
            @param artist ids as [int]
        """
        LazyLoadingView.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_ids = genre_ids
        self._artist_ids = artist_ids
        self._albumsongs = None
        self._context_widget = None
        self._press_rect = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.connect('button-press-event', self._on_button_press)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._scrolled.set_property('expand', True)
        self._scrolled.set_property('height-request', 10)
        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolled, True, False)
        self._paned.pack2(self._context, False, False)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self, albums):
        """
            Populate albums
            @param is compilation as bool
        """
        self._add_albums(albums)

    def show_context(self, show):
        """
            Hide context widget
            @param show as bool
        """
        if show:
            if self._context_widget is not None:
                self._context.show()
        elif self._context_widget is not None:
            self._context.hide()

    def stop(self):
        """
            Stop loading
        """
        self._lazy_queue = []
        for child in self._get_children():
            child.stop()

#######################
# PRIVATE             #
#######################

    def _update_overlays(self, widgets):
        """
            Update children's overlay
            @param widgets as AlbumWidget
        """
        if self._context_widget in widgets:
            widgets.remove(self._context_widget)
        View._update_overlays(self, widgets)

    def _init_context_position(self):
        """
            Init context position if needed
            See _on_position_notify()
        """
        if self._paned.get_position() == 0:
            height = Lp().settings.get_value(
                'paned-context-height').get_int32()
            # We set a stupid max value, safe as self._context is shrinked
            if height == -1:
                height = Lp().window.get_allocated_height()
            else:
                height = self._paned.get_allocated_height() - height
            self._paned.set_position(height)

    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumContextWidget(album_id, self._genre_ids,
                                                  self._artist_ids, size_group)
        self._context_widget.connect('populated', self._on_context_populated)
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)
        # We delay populate() to be sure widget get it size allocated
        GLib.idle_add(self._context_widget.populate)

    def _add_albums(self, albums):
        """
            Add albums to the view
            Start lazy loading
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0), self._genre_ids,
                                       self._artist_ids)
            self._albumbox.insert(widget, -1)
            widget.show()
            self._lazy_queue.append(widget)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False
            GLib.idle_add(self.lazy_loading)
            if self._viewport.get_child() is None:
                self._viewport.add(self._albumbox)

    def _on_context_populated(self, widget):
        """
            Populate again if needed
            @param widget as AlbumContextWidget
        """
        if not widget.is_populated():
            widget.populate()
        elif not self._stop:
            GLib.idle_add(self._context_widget.populate)
        else:
            self._stop = False

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        # we want position of context, not of main view,
        # because we do not get position notify if context hidden
        position = paned.get_allocated_height() - paned.get_position()
        Lp().settings.set_value('paned-context-height',
                                GLib.Variant('i', position))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @param child as Gtk.FlowboxChild
        """
        album_widget = child.get_child()
        if self._press_rect is None:
            if self._context_album_id == album_widget.get_id():
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            else:
                self._init_context_position()
                self._context_album_id = album_widget.get_id()
                self._populate_context(self._context_album_id)
                self._context.show()
        else:
            if self._context_album_id is not None:
                self._context_album_id = None
                self._context.hide()
                self._context_widget.destroy()
                self._context_widget = None
            popover = AlbumPopoverWidget(album_widget.get_id(),
                                         self._genre_ids, self._artist_ids)
            popover.set_relative_to(album_widget)
            popover.set_pointing_to(self._press_rect)
            album_widget.update_overlay()
            popover.show()

    def _on_button_press(self, flowbox, event):
        """
            Store pressed button
            @param flowbox as Gtk.Flowbox
            @param event as Gdk.EventButton
        """
        if event.button == 1:
            self._press_rect = None
        else:
            self._press_rect = Gdk.Rectangle()
            self._press_rect.x = event.x
            self._press_rect.y = event.y
            self._press_rect.width = self._press_rect.height = 1
            event.button = 1
Exemple #48
0
class AlbumsView(View):
    """
        Show albums in a box
    """

    def __init__(self, genre_id, is_compilation):
        """
            Init album view
            @param genre id as int
            @param is compilation as bool
        """
        View.__init__(self)
        self._signal = None
        self._context_album_id = None
        self._genre_id = genre_id
        self._is_compilation = is_compilation
        self._albumsongs = None
        self._context_widget = None

        self._albumbox = Gtk.FlowBox()
        self._albumbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._albumbox.connect('child-activated', self._on_album_activated)
        self._albumbox.set_property('column-spacing', 5)
        self._albumbox.set_property('row-spacing', 5)
        self._albumbox.set_homogeneous(True)
        self._albumbox.set_max_children_per_line(1000)
        self._albumbox.show()

        self._viewport.set_property('valign', Gtk.Align.START)
        self._viewport.set_property('margin', 5)
        self._viewport.add(self._albumbox)
        self._scrolledWindow.set_property('expand', True)

        self._context = ViewContainer(500)

        separator = Gtk.Separator()
        separator.show()

        self._paned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)
        self._paned.pack1(self._scrolledWindow, True, False)
        self._paned.pack2(self._context, False, False)
        height = Lp.settings.get_value('paned-context-height').get_int32()
        # We set a stupid max value, safe as self._context is shrinked
        if height == -1:
            height = Lp.window.get_allocated_height()
        self._paned.set_position(height)
        self._paned.connect('notify::position', self._on_position_notify)
        self._paned.show()
        self.add(self._paned)

    def populate(self):
        """
            Populate albums
            @param is compilation as bool
            @thread safe
        """
        albums = self._get_albums()
        GLib.idle_add(self._add_albums, albums)

#######################
# PRIVATE             #
#######################
    def _get_albums(self):
        """
            Get albums
            @return album ids as [int]
            @thread safe
        """
        sql = Lp.db.get_cursor()
        if self._genre_id == Type.ALL:
            if self._is_compilation:
                albums = Lp.albums.get_compilations(None,
                                                    sql)
            else:
                albums = Lp.albums.get_ids(None, None, sql)
        elif self._genre_id == Type.POPULARS:
            albums = Lp.albums.get_populars(sql)
        elif self._genre_id == Type.RECENTS:
            albums = Lp.albums.get_recents(sql)
        elif self._genre_id == Type.RANDOMS:
            albums = Lp.albums.get_randoms(sql)
        elif self._is_compilation:
            albums = Lp.albums.get_compilations(self._genre_id,
                                                sql)
        else:
            albums = []
            if Lp.settings.get_value('show-compilations'):
                albums += Lp.albums.get_compilations(self._genre_id,
                                                    sql)
            albums += Lp.albums.get_ids(None, self._genre_id, sql)
        sql.close()
        return albums

    def _get_children(self):
        """
            Return view children
            @return [AlbumWidget]
        """
        children = []
        for child in self._albumbox.get_children():
            widget = child.get_child()
            children.append(widget)
        if self._context_widget is not None:
            children.append(self._context_widget)
        return children

    def _populate_context(self, album_id):
        """
            populate context view
            @param album id as int
        """
        size_group = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
        self._context_widget = AlbumDetailedWidget(album_id,
                                                   self._genre_id,
                                                   True,
                                                   True,
                                                   size_group)
        start_new_thread(self._context_widget.populate, ())
        self._context_widget.show()
        view = AlbumContextView(self._context_widget)
        view.show()
        self._context.add(view)
        self._context.set_visible_child(view)
        self._context.clean_old_views(view)

    def _add_albums(self, albums):
        """
            Pop an album and add it to the view,
            repeat operation until album list is empty
            @param [album ids as int]
        """
        if albums and not self._stop:
            widget = AlbumSimpleWidget(albums.pop(0))
            widget.show()
            self._albumbox.insert(widget, -1)
            GLib.idle_add(self._add_albums, albums)
        else:
            self._stop = False

    def _on_position_notify(self, paned, param):
        """
            Save paned position
            @param paned as Gtk.Paned
            @param param as Gtk.Param
        """
        Lp.settings.set_value('paned-context-height',
                              GLib.Variant('i', paned.get_position()))
        return False

    def _on_album_activated(self, flowbox, child):
        """
            Show Context view for activated album
            @param flowbox as Gtk.Flowbox
            @child as Gtk.FlowboxChild
        """
        if self._context_album_id == child.get_child().get_id():
            self._context_album_id = None
            self._context.hide()
            self._context_widget.destroy()
            self._context_widget = None
        else:
            self._context_album_id = child.get_child().get_id()
            if Lp.settings.get_value('auto-play'):
                Lp.player.play_album(self._context_album_id, self._genre_id)
            self._populate_context(self._context_album_id)
            self._context.show()
Exemple #49
0
class PopAlbums(Gtk.Popover):

    """
        Init Popover ui with a text entry and a scrolled treeview
    """
    def __init__(self):
        Gtk.Popover.__init__(self)
        self._stack = ViewContainer(1000)
        self._stack.show()

        self._on_screen_artist = None
        self.add(self._stack)

        Objects.player.connect("current-changed", self._update_content)

    """
        Run _populate in a thread
    """
    def populate(self):
        if self._on_screen_artist != Objects.player.current.aartist_id:
            self._on_screen_artist = Objects.player.current.aartist_id
            view = ArtistView(self._on_screen_artist, False)
            view.show()
            start_new_thread(view.populate, (None,))
            self._stack.add(view)
            self._stack.set_visible_child(view)
            self._stack.clean_old_views(view)

    """
        Resize popover
    """
    def do_show(self):
        size_setting = Objects.settings.get_value('window-size')
        if isinstance(size_setting[0], int) and\
           isinstance(size_setting[1], int):
            self.set_size_request(size_setting[0]*0.65, size_setting[1]*0.8)
        else:
            self.set_size_request(600, 600)
        Gtk.Popover.do_show(self)

    """
        Remove view
    """
    def do_hide(self):
        Gtk.Popover.do_hide(self)
        child = self._stack.get_visible_child()
        child.stop()
        self._on_screen_artist = None
        self._stack.clean_old_views(None)

#######################
# PRIVATE             #
#######################
    """
        Update the content view
        @param player as Player
        @param track id as int
    """
    def _update_content(self, player):
        if self.is_visible():
            self.populate()