Beispiel #1
0
    def _setup_view(self):
        self._paned_main_list = Gtk.HPaned()
        self._paned_list_view = Gtk.HPaned()
        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()

        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()
Beispiel #2
0
    def _setup_view(self):
        self._paned_main_list = Gtk.HPaned()
        self._paned_list_view = Gtk.HPaned()
        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()

        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()
Beispiel #3
0
    def right_list(self):
        """
            Get right selection list
            @return SelectionList
        """
        def on_unmap(widget):
            """
                Hide right list on left list hidden
            """
            if not App().window.folded:
                self._hide_right_list()

        if self.__right_list is None:
            eventbox = Gtk.EventBox.new()
            eventbox.set_size_request(50, -1)
            eventbox.show()
            self.__right_list_grid = Gtk.Grid()
            style_context = self.__right_list_grid.get_style_context()
            style_context.add_class("left-gradient")
            style_context.add_class("opacity-transition-fast")
            self.__right_list = SelectionList(SelectionListMask.FASTSCROLL)
            self.__right_list.show()
            self.__right_list.scrolled.set_size_request(250, -1)
            self.__gesture = GesturesHelper(
                eventbox, primary_press_callback=self._hide_right_list)
            self.__right_list.listbox.connect("row-activated",
                                              self.__on_right_list_activated)
            self.__right_list_grid.add(eventbox)
            self.__right_list_grid.add(self.__right_list)
            self.__left_list.overlay.add_overlay(self.__right_list_grid)
            self.__left_list.connect("unmap", on_unmap)
            self.__left_list.connect("populated", self.__on_list_populated)
            self.__right_list.connect("populated", self.__on_list_populated)
        return self.__right_list
Beispiel #4
0
    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()
Beispiel #5
0
	def _setup_view(self):
		self._paned_main_list = Gtk.HPaned()
		self._paned_list_view = Gtk.HPaned()
		vgrid = Gtk.Grid()
		vgrid.set_orientation(Gtk.Orientation.VERTICAL)
	
		self._toolbar = Toolbar()
		self._toolbar.header_bar.show()
		self._toolbar.get_view_genres_btn().connect("toggled", self._setup_list_one)

		self._list_one = SelectionList("Genre")
		self._list_two = SelectionList("Artist")
		self._list_one_signal = None
		self._list_two_signal = None
		
		self._loading = True
		loading_view = LoadingView()

		self._stack = Gtk.Stack()
		self._stack.add(loading_view)
		self._stack.set_visible_child(loading_view)
		self._stack.set_transition_duration(500)
		self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
		self._stack.show()

		self._progress = Gtk.ProgressBar()

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

		DESKTOP = environ.get("XDG_CURRENT_DESKTOP")
		if DESKTOP and ("GNOME" in DESKTOP or "Pantheon" in DESKTOP):
			self.set_titlebar(self._toolbar.header_bar)
			self._toolbar.header_bar.set_show_close_button(True)
			self.add(self._paned_main_list)
		else:
			hgrid = Gtk.Grid()
			hgrid.set_orientation(Gtk.Orientation.VERTICAL)
			hgrid.add(self._toolbar.header_bar)
			hgrid.add(self._paned_main_list)
			hgrid.show()
			self.add(hgrid)

		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()
		self.show()
Beispiel #6
0
 def left_list(self):
     """
         Get left selection list
         @return SelectionList
     """
     if self.__left_list is None:
         self.__left_list = SelectionList(SelectionListMask.FASTSCROLL)
         self.__left_list.listbox.connect("row-activated",
                                          self.__on_left_list_activated)
         self.__left_list.scrolled.set_size_request(300, -1)
     return self.__left_list
 def __init__(self):
     """
         Init container
     """
     self._list_one = SelectionList(SelectionListMask.LIST_ONE)
     self._list_two = SelectionList(SelectionListMask.LIST_TWO)
     self._list_one.connect("item-selected", self.__on_list_one_selected)
     self._list_one.connect("populated", self.__on_list_one_populated)
     self._list_one.connect("pass-focus", self.__on_pass_focus)
     self._list_two.connect("item-selected", self.__on_list_two_selected)
     self._list_two.connect("pass-focus", self.__on_pass_focus)
Beispiel #8
0
 def __init__(self):
     """
         Init container
     """
     self.__left_list = None
     self.__right_list = None
     self._sidebar = SelectionList(SelectionListMask.SIDEBAR)
     self._sidebar.show()
     self._sidebar.listbox.connect("row-activated",
                                   self.__on_sidebar_activated)
     self._sidebar.connect("populated", self.__on_sidebar_populated)
     items = ShownLists.get(SelectionListMask.SIDEBAR)
     self._sidebar.populate(items)
     self._stack.connect("set-sidebar-id", self.__on_set_sidebar_id)
     self._stack.connect("set-selection-ids", self.__on_set_selection_ids)
Beispiel #9
0
	def _setup_view(self):
		self._box = Gtk.Grid()
		self._toolbar = Toolbar(self._db, self._player)
		self.set_titlebar(self._toolbar.header_bar)
		self._toolbar.header_bar.show()
		self._toolbar.get_infobox().connect("button-press-event", self._show_current_album)

		self._list_genres = SelectionList("Genre", 150)
		self._list_artists = SelectionList("Artist", 200)
		
		self._view = LoadingView()

		separator = Gtk.Separator()
		separator.show()
		self._box.add(self._list_genres.widget)
		self._box.add(separator)
		self._box.add(self._list_artists.widget)
		self._box.add(self._view)
		self.add(self._box)
		self._box.show()
		self.show()
Beispiel #10
0
 def _setup_lists(self):
     """
         Add and setup list one and list two
     """
     self._list_one = SelectionList(SelectionListMask.LIST_ONE)
     self._list_two = SelectionList(SelectionListMask.LIST_TWO)
     self._list_one.listbox.connect("row-activated",
                                    self.__on_list_one_activated)
     self._list_two.listbox.connect("row-activated",
                                    self.__on_list_two_activated)
     self._list_one.connect("populated", self.__on_list_one_populated)
     self._list_one.connect("pass-focus", self.__on_pass_focus)
     self._list_two.connect("pass-focus", self.__on_pass_focus)
     self._list_two.connect("map", self.__on_list_two_mapped)
     self._paned_two.add1(self._list_two)
     self._paned_one.add1(self._list_one)
     App().window.add_paned(self._paned_one, self._list_one)
     App().window.add_paned(self._paned_two, self._list_two)
     if App().window.is_adaptive:
         self._list_one.show()
         App().window.update_layout(True)
Beispiel #11
0
    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_one.connect("pass-focus", self.__on_pass_focus)
        self.__list_two.connect("item-selected", self.__on_list_two_selected)
        self.__list_two.connect("pass-focus", self.__on_pass_focus)

        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 __init__(self, device):
        """
            Init view
            @param device as Device
        """
        View.__init__(self)
        self.__timeout_id = None
        self.__device = device
        self.__selected_ids = []
        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/DeviceManagerView.ui")
        self.__memory_combo = builder.get_object("memory_combo")
        self.__syncing_btn = builder.get_object("sync_btn")
        # FIXME Wait for translation
        _("Synchronize")
        self.__syncing_btn.set_label(_("Synchronize %s") % "")
        builder.connect_signals(self)
        self.__device_widget = DeviceManagerWidget(self)
        self.__device_widget.mtp_sync.connect("sync-finished",
                                              self.__on_sync_finished)
        self.__device_widget.mtp_sync.connect("sync-errors",
                                              self.__on_sync_errors)
        self.__device_widget.show()
        self.__infobar = builder.get_object("infobar")
        self.__error_label = builder.get_object("error_label")
        self.__paned = builder.get_object("paned")
        self.__selection_list = SelectionList(SelectionListMask.LIST_ONE)
        self.__selection_list.connect("item-selected", self.__on_item_selected)
        self.__selection_list.mark_as(SelectionListMask.ARTISTS)
        self.__selection_list.show()
        self.__paned.add1(self.__selection_list)
        self.__paned.add2(builder.get_object("device_view"))
        builder.get_object("device_view").attach(self._scrolled, 0, 3, 4, 1)
        self.add(self.__paned)
        self.__paned.set_position(
            App().settings.get_value("paned-device-width").get_int32())

        self.__update_list_device()
        self.__sanitize_non_mtp()
Beispiel #13
0
    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()
Beispiel #14
0
    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()
Beispiel #15
0
    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()
Beispiel #16
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)
Beispiel #17
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)
class ListsContainer:
    """
        Selections lists management for main view
    """
    def __init__(self):
        """
            Init container
        """
        self._list_one = SelectionList(SelectionListMask.LIST_ONE)
        self._list_two = SelectionList(SelectionListMask.LIST_TWO)
        self._list_one.connect("item-selected", self.__on_list_one_selected)
        self._list_one.connect("populated", self.__on_list_one_populated)
        self._list_one.connect("pass-focus", self.__on_pass_focus)
        self._list_two.connect("item-selected", self.__on_list_two_selected)
        self._list_two.connect("pass-focus", self.__on_pass_focus)

    def update_list_one(self, update=False):
        """
            Update list one
            @param update as bool
        """
        if self._list_one.get_visible():
            if App().settings.get_value("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, update=False):
        """
            Update list two
            @param update as bool
        """
        if self._list_one.get_visible():
            ids = self._list_one.selected_ids
            if ids and ids[0] in [Type.PLAYLISTS, Type.YEARS]:
                self.__update_list_playlists(update, ids[0])
            elif App().settings.get_value("show-genres") and ids:
                self.__update_list_artists(self._list_two, ids, update)

    def show_artists_albums(self, artist_ids):
        """
            Show albums from artists
            @param artist id as int
        """
        def select_list_two(selection_list, artist_ids):
            self._list_two.select_ids(artist_ids)
            self._list_two.disconnect_by_func(select_list_two)

        self._list_one.select_ids()
        self._list_two.select_ids()
        if App().settings.get_value("show-genres"):
            # Get artist genres
            genre_ids = []
            for artist_id in artist_ids:
                album_ids = App().artists.get_albums(artist_ids)
                for album_id in album_ids:
                    for genre_id in App().albums.get_genre_ids(album_id):
                        if genre_id not in genre_ids:
                            genre_ids.append(genre_id)
            # Select genres on list one
            self._list_two.connect("populated", select_list_two, artist_ids)
            self._list_one.select_ids(genre_ids)
        else:
            # Select artists on list one
            self._list_one.select_ids(artist_ids)

    @property
    def list_one(self):
        """
            Get first SelectionList
            @return SelectionList
        """
        return self._list_one

    @property
    def list_two(self):
        """
            Get second SelectionList
            @return SelectionList
        """
        return self._list_two

##############
# PROTECTED  #
##############

    def _reload_list_view(self):
        """
            Reload list view
        """
        def select_list_two(selection_list, ids):
            # For some reasons, we need to delay this
            # If list two list is short, we may receive list two selected-item
            # signal before list one
            GLib.idle_add(self._list_two.select_ids, ids)
            self._list_two.disconnect_by_func(select_list_two)

        state_one_ids = App().settings.get_value("state-one-ids")
        state_two_ids = App().settings.get_value("state-two-ids")
        if state_two_ids and not state_one_ids:
            if App().settings.get_value("show-genres"):
                self.show_artists_albums(state_two_ids)
                return
            else:
                state_one_ids = state_two_ids
                state_two_ids = []
        if state_two_ids:
            self._list_two.connect("populated", select_list_two, state_two_ids)
        self._list_one.select_ids()
        if state_one_ids:
            self._list_one.select_ids(state_one_ids)
        else:
            self._list_one.select_first()

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

    def __update_list_playlists(self, update, type):
        """
            Setup list for playlists
            @param update as bool
            @param type as int
        """
        self._list_two.mark_as(SelectionListMask.PLAYLISTS)
        if type == Type.PLAYLISTS:
            items = self._list_two.get_playlist_headers()
            items += App().playlists.get()
        else:
            (years, unknown) = App().albums.get_years()
            items = [(year, str(year), str(year)) for year in sorted(years)]
            if unknown:
                items.insert(0, (Type.NONE, _("Unknown"), ""))
        if update:
            self._list_two.update_values(items)
        else:
            self._list_two.populate(items)

    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
        """
        def load():
            genres = App().genres.get()
            return genres

        def setup(genres):
            selection_list.mark_as(SelectionListMask.GENRE)
            items = selection_list.get_headers(selection_list.mask)
            items += genres
            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
        """
        def load():
            artists = App().artists.get(genre_ids)
            compilations = App().albums.get_compilation_ids(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            mask = SelectionListMask.ARTISTS
            if compilations:
                mask |= SelectionListMask.COMPILATIONS
            selection_list.mark_as(mask)
            items = selection_list.get_headers(selection_list.mask)
            items += artists
            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 __on_list_one_selected(self, selection_list):
        """
            Update view based on selected object
            @param list as SelectionList
        """
        Logger.debug("Container::__on_list_one_selected()")
        self._stack.destroy_non_visible_children()
        if not App().window.is_adaptive:
            App().window.emit("show-can-go-back", False)
            App().window.emit("can-go-back-changed", False)
        view = None
        selected_ids = self._list_one.selected_ids
        if not selected_ids:
            return
        # Update lists
        if selected_ids[0] in [Type.PLAYLISTS, Type.YEARS]:
            self.__update_list_playlists(False, selected_ids[0])
            self._list_two.show()
        elif (selected_ids[0] > 0 or selected_ids[0] == Type.ALL) and\
                self._list_one.mask & SelectionListMask.GENRE:
            self.__update_list_artists(self._list_two, selected_ids, False)
            self._list_two.show()
        else:
            self._list_two.hide()
        # Update view
        if selected_ids[0] == Type.PLAYLISTS:
            if not self._list_two.selected_ids:
                view = self._get_view_playlists()
        elif selected_ids[0] == Type.CURRENT:
            view = self._get_view_current()
        elif selected_ids[0] == Type.SEARCH:
            view = self._get_view_search()
        elif Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES:
            view = self._get_view_device(selected_ids[0])
        elif selected_ids[0] in [
                Type.POPULARS, Type.LOVED, Type.RECENTS, Type.NEVER,
                Type.RANDOMS
        ]:
            view = self._get_view_albums(selected_ids, [])
        elif selected_ids[0] == Type.RADIOS:
            view = self._get_view_radios()
        elif selected_ids[0] == Type.YEARS:
            view = self._get_view_albums_decades()
        elif selected_ids[0] == Type.ARTISTS:
            view = self._get_view_artists_rounded(False)
            App().window.emit("show-can-go-back", True)
        elif selection_list.mask & SelectionListMask.ARTISTS:
            if selected_ids[0] == Type.ALL:
                view = self._get_view_albums(selected_ids, [])
            elif selected_ids[0] == Type.COMPILATIONS:
                view = self._get_view_albums([], selected_ids)
            else:
                view = self._get_view_artists([], selected_ids)
        else:
            view = self._get_view_albums(selected_ids, [])
        if view is not None:
            if App().window.is_adaptive:
                App().window.emit("can-go-back-changed", True)
            if view not in self._stack.get_children():
                self._stack.add(view)
            # If we are in paned stack mode, show list two if wanted
            if App().window.is_adaptive\
                    and self._list_two.is_visible()\
                    and (
                        selected_ids[0] >= 0 or
                        Type.DEVICES - 999 < selected_ids[0] < Type.DEVICES or
                        selected_ids[0] in [Type.PLAYLISTS,
                                            Type.YEARS,
                                            Type.ALL]):
                self._stack.set_visible_child(self._list_two)
            else:
                self._stack.set_visible_child(view)

    def __on_list_one_populated(self, selection_list):
        """
            Add device to list one
            @param selection_list as SelectionList
        """
        for dev in self.devices.values():
            self._list_one.add_value((dev.id, dev.name, dev.name))

    def __on_list_two_selected(self, selection_list):
        """
            Update view based on selected object
            @param selection_list as SelectionList
        """
        Logger.debug("Container::__on_list_two_selected()")
        self._stack.destroy_non_visible_children()
        if not App().window.is_adaptive:
            App().window.emit("show-can-go-back", False)
            App().window.emit("can-go-back-changed", False)
        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:
            view = self._get_view_playlists(selected_ids)
        elif genre_ids[0] == Type.YEARS:
            view = self._get_view_albums_years(selected_ids)
        elif selected_ids[0] == Type.COMPILATIONS:
            view = self._get_view_albums(genre_ids, selected_ids)
        else:
            view = self._get_view_artists(genre_ids, selected_ids)
        self._stack.add(view)
        self._stack.set_visible_child(view)

    def __on_pass_focus(self, selection_list):
        """
            Pass focus to other list
            @param selection_list as SelectionList
        """
        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.grab_focus()
        else:
            self._list_one.grab_focus()
Beispiel #19
0
    def __init__(self, parent):
        """
            Init widget
            @param device as Device
            @param parent as Gtk.Widget
        """
        Gtk.Bin.__init__(self)
        MtpSync.__init__(self)
        self.__parent = parent
        self.__stop = False
        self._uri = None

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/DeviceManagerWidget.ui')
        widget = builder.get_object('widget')
        self.__error_label = builder.get_object('error-label')
        self.__switch_albums = builder.get_object('switch_albums')
        self.__switch_albums.set_state(Lp().settings.get_value('sync-albums'))
        self.__switch_mp3 = builder.get_object('switch_mp3')
        self.__switch_normalize = builder.get_object('switch_normalize')
        if not self._check_encoder_status():
            self.__switch_mp3.set_sensitive(False)
            self.__switch_normalize.set_sensitive(False)
            self.__switch_mp3.set_tooltip_text(_("You need to install " +
                                               "gstreamer-plugins-ugly"))
        else:
            self.__switch_mp3.set_state(Lp().settings.get_value('convert-mp3'))
        self.__menu_items = builder.get_object('menu-items')
        self.__menu = builder.get_object('menu')

        self.__model = Gtk.ListStore(bool, str, int)

        self.__selection_list = SelectionList(False)
        self.__selection_list.connect('item-selected',
                                      self.__on_item_selected)
        widget.attach(self.__selection_list, 1, 1, 1, 1)
        self.__selection_list.set_hexpand(True)

        self.__view = builder.get_object('view')
        self.__view.set_model(self.__model)

        builder.connect_signals(self)

        self.add(widget)

        self.__infobar = builder.get_object('infobar')
        self.__infobar_label = builder.get_object('infobarlabel')

        renderer0 = Gtk.CellRendererToggle()
        renderer0.set_property('activatable', True)
        renderer0.connect('toggled', self.__on_item_toggled)
        column0 = Gtk.TreeViewColumn(" ✓", renderer0, active=0)
        column0.set_clickable(True)
        column0.connect('clicked', self.__on_column0_clicked)

        renderer1 = CellRendererAlbum()
        self.__column1 = Gtk.TreeViewColumn("", renderer1, album=2)

        renderer2 = Gtk.CellRendererText()
        renderer2.set_property('ellipsize-set', True)
        renderer2.set_property('ellipsize', Pango.EllipsizeMode.END)
        self.__column2 = Gtk.TreeViewColumn("", renderer2, markup=1)
        self.__column2.set_expand(True)

        self.__view.append_column(column0)
        self.__view.append_column(self.__column1)
        self.__view.append_column(self.__column2)
Beispiel #20
0
class DeviceManagerWidget(Gtk.Bin, MtpSync):
    """
        Widget for synchronize mtp devices
    """
    __gsignals__ = {
        'sync-finished': (GObject.SignalFlags.RUN_FIRST, None, ())
    }

    def __init__(self, parent):
        """
            Init widget
            @param device as Device
            @param parent as Gtk.Widget
        """
        Gtk.Bin.__init__(self)
        MtpSync.__init__(self)
        self.__parent = parent
        self.__stop = False
        self._uri = None

        builder = Gtk.Builder()
        builder.add_from_resource('/org/gnome/Lollypop/DeviceManagerWidget.ui')
        widget = builder.get_object('widget')
        self.__error_label = builder.get_object('error-label')
        self.__switch_albums = builder.get_object('switch_albums')
        self.__switch_albums.set_state(Lp().settings.get_value('sync-albums'))
        self.__switch_mp3 = builder.get_object('switch_mp3')
        self.__switch_normalize = builder.get_object('switch_normalize')
        if not self._check_encoder_status():
            self.__switch_mp3.set_sensitive(False)
            self.__switch_normalize.set_sensitive(False)
            self.__switch_mp3.set_tooltip_text(_("You need to install " +
                                               "gstreamer-plugins-ugly"))
        else:
            self.__switch_mp3.set_state(Lp().settings.get_value('convert-mp3'))
        self.__menu_items = builder.get_object('menu-items')
        self.__menu = builder.get_object('menu')

        self.__model = Gtk.ListStore(bool, str, int)

        self.__selection_list = SelectionList(False)
        self.__selection_list.connect('item-selected',
                                      self.__on_item_selected)
        widget.attach(self.__selection_list, 1, 1, 1, 1)
        self.__selection_list.set_hexpand(True)

        self.__view = builder.get_object('view')
        self.__view.set_model(self.__model)

        builder.connect_signals(self)

        self.add(widget)

        self.__infobar = builder.get_object('infobar')
        self.__infobar_label = builder.get_object('infobarlabel')

        renderer0 = Gtk.CellRendererToggle()
        renderer0.set_property('activatable', True)
        renderer0.connect('toggled', self.__on_item_toggled)
        column0 = Gtk.TreeViewColumn(" ✓", renderer0, active=0)
        column0.set_clickable(True)
        column0.connect('clicked', self.__on_column0_clicked)

        renderer1 = CellRendererAlbum()
        self.__column1 = Gtk.TreeViewColumn("", renderer1, album=2)

        renderer2 = Gtk.CellRendererText()
        renderer2.set_property('ellipsize-set', True)
        renderer2.set_property('ellipsize', Pango.EllipsizeMode.END)
        self.__column2 = Gtk.TreeViewColumn("", renderer2, markup=1)
        self.__column2.set_expand(True)

        self.__view.append_column(column0)
        self.__view.append_column(self.__column1)
        self.__view.append_column(self.__column2)

    def populate(self):
        """
            Populate playlists
            @thread safe
        """
        self.__model.clear()
        self.__stop = False
        if Lp().settings.get_value('sync-albums'):
            self.__selection_list.clear()
            self.__setup_list_artists(self.__selection_list)
            self.__column1.set_visible(True)
            self.__column2.set_title(_("Albums"))
            self.__selection_list.show()
            self.__selection_list.select_ids([Type.ALL])
        else:
            playlists = [(Type.LOVED, Lp().playlists.LOVED)]
            playlists += Lp().playlists.get()
            self.__append_playlists(playlists)
            self.__column1.set_visible(False)
            self.__column2.set_title(_("Playlists"))
            self.__selection_list.hide()

    def set_uri(self, uri):
        """
            Set uri
            @param uri as str
        """
        self._uri = uri
        d = Lio.File.new_for_uri(uri)
        try:
            if not d.query_exists():
                d.make_directory_with_parents()
        except:
            pass

    def is_syncing(self):
        """
            @return True if syncing
        """
        return self._syncing

    def sync(self):
        """
            Start synchronisation
        """
        self._syncing = True
        Lp().window.progress.add(self)
        self.__menu.set_sensitive(False)
        playlists = []
        if not Lp().settings.get_value('sync-albums'):
            self.__view.set_sensitive(False)
            for item in self.__model:
                if item[0]:
                    playlists.append(item[2])
        else:
            playlists.append(Type.NONE)

        t = Thread(target=self._sync,
                   args=(playlists,
                         self.__switch_mp3.get_active(),
                         self.__switch_normalize.get_active()))
        t.daemon = True
        t.start()

    def cancel_sync(self):
        """
            Cancel synchronisation
        """
        self._syncing = False

    def show_overlay(self, bool):
        """
            No overlay here now
        """
        pass

#######################
# PROTECTED           #
#######################
    def _update_progress(self):
        """
            Update progress bar smoothly
        """
        current = Lp().window.progress.get_fraction()
        if self._syncing:
            progress = (self._fraction-current)/10
        else:
            progress = 0.01
        if current < self._fraction:
            Lp().window.progress.set_fraction(current+progress, self)
        if current < 1.0:
            if progress < 0.0002:
                GLib.timeout_add(500, self._update_progress)
            else:
                GLib.timeout_add(25, self._update_progress)
        else:
            GLib.timeout_add(1000, self._on_finished)

    def _pop_menu(self, button):
        """
            Popup menu for album
            @param button as Gtk.Button
            @param album id as int
        """
        parent = self.__menu_items.get_parent()
        if parent is not None:
            parent.remove(self.__menu_items)
        popover = Gtk.Popover.new(button)
        popover.set_position(Gtk.PositionType.BOTTOM)
        popover.add(self.__menu_items)
        popover.show()

    def _on_finished(self):
        """
            Emit finished signal
        """
        MtpSync._on_finished(self)
        Lp().window.progress.set_fraction(1.0, self)
        if not self.__switch_albums.get_state():
            self.__view.set_sensitive(True)
        self.__menu.set_sensitive(True)
        self.emit('sync-finished')

    def _on_errors(self):
        """
            Show information bar with error message
        """
        MtpSync._on_errors(self)
        error_text = _("Unknown error while syncing,"
                       " try to reboot your device")
        try:
            d = Lio.File.new_for_uri(self._uri)
            info = d.query_filesystem_info('filesystem::free')
            free = info.get_attribute_as_string('filesystem::free')

            if free is None or int(free) < 1024:
                error_text = _("No free space available on device")
        except Exception as e:
            print("DeviceWidget::_on_errors(): %s" % e)
        self.__error_label.set_text(error_text)
        self.__infobar.show()

    def _on_albums_state_set(self, widget, state):
        """
            Enable or disable playlist selection
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        self.__stop = True
        Lp().settings.set_value('sync-albums', GLib.Variant('b', state))
        GLib.idle_add(self.populate)

    def _on_mp3_state_set(self, widget, state):
        """
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        Lp().settings.set_value('convert-mp3', GLib.Variant('b', state))
        if not state:
            self.__switch_normalize.set_active(False)
            Lp().settings.set_value('normalize-mp3',
                                    GLib.Variant('b', False))

    def _on_normalize_state_set(self, widget, state):
        """
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        Lp().settings.set_value('normalize-mp3', GLib.Variant('b', state))
        if state:
            self.__switch_mp3.set_active(True)
            Lp().settings.set_value('convert-mp3', GLib.Variant('b', True))

    def _on_response(self, infobar, response_id):
        """
            Hide infobar
            @param widget as Gtk.Infobar
            @param reponse id as int
        """
        if response_id == Gtk.ResponseType.CLOSE:
            self.__infobar.hide()

#######################
# PRIVATE             #
#######################
    def __setup_list_artists(self, selection_list):
        """
            Setup list for artists
            @param list as SelectionList
            @thread safe
        """
        def load():
            artists = Lp().artists.get_local()
            compilations = Lp().albums.get_compilation_ids()
            return (artists, compilations)

        def setup(artists, compilations):
            items = []
            items.append((Type.ALL, _("Synced albums")))
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
                items.append((Type.SEPARATOR, ''))
            items += artists
            selection_list.mark_as_artists(True)
            selection_list.populate(items)
        loader = Loader(target=load, view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def __append_playlists(self, playlists, files_list=[]):
        """
            Append a playlist
            @param playlists as [(int, str)]
            @internal files_list
        """
        if playlists and not self.__stop:
            # Cache directory playlists
            if not files_list:
                try:
                    d = Lio.File.new_for_uri(self._uri)
                    infos = d.enumerate_children(
                                            'standard::name,standard::type',
                                            Gio.FileQueryInfoFlags.NONE,
                                            None)
                    for info in infos:
                        if info.get_file_type() != Gio.FileType.DIRECTORY:
                            f = infos.get_child(info)
                            if f.get_uri().endswith(".m3u"):
                                files_list.append(
                                          GLib.path_get_basename(f.get_path()))
                except:
                    pass
            playlist = playlists.pop(0)
            selected = playlist[1] + ".m3u" in files_list
            self.__model.append([selected, playlist[1], playlist[0]])
            GLib.idle_add(self.__append_playlists, playlists, files_list)

    def __append_albums(self, albums):
        """
            Append albums
            @param albums as [int]
        """
        if albums and not self.__stop:
            album = Album(albums.pop(0))
            synced = Lp().albums.get_synced(album.id)
            # Do not sync youtube albums
            if synced != Type.NONE:
                if album.artist_ids[0] == Type.COMPILATIONS:
                    name = GLib.markup_escape_text(album.name)
                else:
                    artists = ", ".join(album.artists)
                    name = "<b>%s</b> - %s" % (
                            GLib.markup_escape_text(artists),
                            GLib.markup_escape_text(album.name))
                self.__model.append([synced, name, album.id])
            GLib.idle_add(self.__append_albums, albums)

    def __populate_albums_playlist(self, album_id, toggle):
        """
            Populate hidden albums playlist
            @param album_id as int
            @param toggle as bool
            @warning commit on default sql cursor needed
        """
        if Lp().settings.get_value('sync-albums'):
            Lp().albums.set_synced(album_id, toggle)

    def __on_item_selected(self, selection_list):
        """
            Show album from artist
            @param selection list as SelectionList
        """
        if not selection_list.selected_ids:
            return
        if selection_list.selected_ids[0] == Type.COMPILATIONS:
            albums = Lp().albums.get_compilation_ids()
        elif selection_list.selected_ids[0] == Type.ALL:
            albums = Lp().albums.get_synced_ids()
        else:
            albums = Lp().albums.get_ids(selection_list.selected_ids)
        self.__model.clear()
        self.__append_albums(albums)

    def __on_column0_clicked(self, column):
        """
            Select/Unselect all playlists
            @param column as Gtk.TreeViewColumn
        """
        selected = False
        for item in self.__model:
            if item[0]:
                selected = True
        for item in self.__model:
            item[0] = not selected
            self.__populate_albums_playlist(item[2], item[0])
        with SqlCursor(Lp().db) as sql:
            sql.commit()

    def __on_item_toggled(self, view, path):
        """
            When item is toggled, set model
            @param widget as cell renderer
            @param path as str representation of Gtk.TreePath
        """
        iterator = self.__model.get_iter(path)
        toggle = not self.__model.get_value(iterator, 0)
        self.__model.set_value(iterator, 0, toggle)
        album_id = self.__model.get_value(iterator, 2)
        self.__populate_albums_playlist(album_id, toggle)
        with SqlCursor(Lp().db) as sql:
            sql.commit()
Beispiel #21
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())
Beispiel #22
0
class ListsContainer:
    """
        Selections lists management for main view
    """

    def __init__(self):
        """
            Init container
        """
        self.__left_list = None
        self.__right_list = None
        self._sidebar = SelectionList(SelectionListMask.SIDEBAR)
        self._sidebar.show()
        self._sidebar.listbox.connect("row-activated",
                                      self.__on_sidebar_activated)
        self._sidebar.connect("populated", self.__on_sidebar_populated)
        self._sidebar.set_mask(SelectionListMask.SIDEBAR)
        items = ShownLists.get(SelectionListMask.SIDEBAR)
        self._sidebar.populate(items)
        self._stack.connect("set-sidebar-id", self.__on_set_sidebar_id)
        self._stack.connect("set-selection-ids", self.__on_set_selection_ids)

    @property
    def sidebar(self):
        """
            Get first SelectionList
            @return SelectionList
        """
        return self._sidebar

    @property
    def left_list(self):
        """
            Get left selection list
            @return SelectionList
        """
        if self.__left_list is None:
            self.__left_list = SelectionList(SelectionListMask.FASTSCROLL)
            self.__left_list.listbox.connect("row-activated",
                                             self.__on_left_list_activated)
            self.__left_list.scrolled.set_size_request(300, -1)
        return self.__left_list

    @property
    def right_list(self):
        """
            Get right selection list
            @return SelectionList
        """
        def on_unmap(widget):
            """
                Hide right list on left list hidden
            """
            if not App().window.folded:
                self._hide_right_list()

        if self.__right_list is None:
            eventbox = Gtk.EventBox.new()
            eventbox.set_size_request(50, -1)
            eventbox.show()
            self.__right_list_grid = Gtk.Grid()
            style_context = self.__right_list_grid.get_style_context()
            style_context.add_class("left-gradient")
            style_context.add_class("opacity-transition-fast")
            self.__right_list = SelectionList(SelectionListMask.FASTSCROLL)
            self.__right_list.show()
            self.__right_list.scrolled.set_size_request(250, -1)
            self.__gesture = GesturesHelper(
                eventbox, primary_press_callback=self._hide_right_list)
            self.__right_list.listbox.connect("row-activated",
                                              self.__on_right_list_activated)
            self.__right_list_grid.add(eventbox)
            self.__right_list_grid.add(self.__right_list)
            self.__left_list.overlay.add_overlay(self.__right_list_grid)
            self.__left_list.connect("unmap", on_unmap)
            self.__left_list.connect("populated", self.__on_list_populated)
            self.__right_list.connect("populated", self.__on_list_populated)
        return self.__right_list

    @property
    def sidebar_id(self):
        """
            Get sidebar id for current state
            @return int
        """
        if self.right_list.selected_ids:
            ids = self.right_list.selected_ids
        elif self.left_list.selected_ids:
            ids = self.left_list.selected_ids
        else:
            ids = self.sidebar.selected_ids
        return ids[0] if ids else Type.NONE

##############
# PROTECTED  #
##############
    def _show_genres_list(self, selection_list):
        """
            Setup list for genres
            @param list as SelectionList
        """
        def load():
            genres = App().genres.get()
            return genres
        selection_list.set_mask(SelectionListMask.GENRES)
        App().task_helper.run(load, callback=(selection_list.populate,))

    def _show_artists_list(self, selection_list, genre_ids=[]):
        """
            Setup list for artists
            @param list as SelectionList
            @param genre_ids as [int]
        """
        def load():
            storage_type = get_default_storage_type()
            artists = App().artists.get(genre_ids, storage_type)
            return artists
        selection_list.set_mask(SelectionListMask.ARTISTS)
        App().task_helper.run(load, callback=(selection_list.populate,))

    def _show_right_list(self):
        """
            Show right list
        """
        if self.__right_list is not None:
            self.__right_list_grid.show()
            self.__right_list_grid.set_state_flags(Gtk.StateFlags.VISITED,
                                                   False)
            self.set_focused_view(self.right_list)

    def _hide_right_list(self, *ignore):
        """
            Hide right list
        """
        if self.__right_list is not None:
            self.__right_list_grid.unset_state_flags(Gtk.StateFlags.VISITED)
            GLib.timeout_add(200, self.__right_list_grid.hide)
            self.__right_list.clear()
            self.set_focused_view(self.left_list)

############
# PRIVATE  #
############
    def __on_sidebar_activated(self, listbox, row):
        """
            Update view based on selected object
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        Logger.debug("Container::__on_sidebar_activated()")
        self.sub_widget.set_visible_child(self.grid_view)
        view = None
        focus_set = False
        selected_id = self._sidebar.selected_id
        if selected_id is None:
            return
        # Update lists
        if selected_id in [Type.ARTISTS_LIST, Type.GENRES_LIST] and\
                self.type_ahead.get_reveal_child() and\
                self.left_list.get_visible():
            self.set_focused_view(self.left_list)
            focus_set = True
        elif selected_id == Type.ARTISTS_LIST:
            self._show_artists_list(self.left_list)
            self._hide_right_list()
            self.left_list.show()
            self.widget.set_visible_child(self.sub_widget)
            self.sub_widget.set_visible_child(self.left_list)
            self.set_focused_view(self.left_list)
            focus_set = True
        elif selected_id == Type.GENRES_LIST:
            self._show_genres_list(self.left_list)
            self._hide_right_list()
            self.left_list.show()
            self.widget.set_visible_child(self.sub_widget)
            self.sub_widget.set_visible_child(self.left_list)
            self.set_focused_view(self.left_list)
            focus_set = True
        else:
            self.left_list.hide()
            self.left_list.clear()
            if self.widget.get_folded():
                self.widget.set_visible_child(self.sub_widget)
                self.sub_widget.set_visible_child(self.grid_view)

        storage_type = get_default_storage_type()
        if selected_id in [Type.ARTISTS_LIST, Type.GENRES_LIST] and not\
                App().window.folded:
            view = NoneView()
            view.show()
        elif selected_id == Type.PLAYLISTS:
            view = self._get_view_playlists()
        elif selected_id == Type.LYRICS:
            view = self._get_view_lyrics()
        elif selected_id == Type.CURRENT:
            view = self.get_view_current()
        elif selected_id == Type.SEARCH:
            view = self.get_view_search()
        elif selected_id == Type.SUGGESTIONS:
            view = self._get_view_suggestions(storage_type)
        elif selected_id in [Type.POPULARS,
                             Type.LOVED,
                             Type.RECENTS,
                             Type.LITTLE,
                             Type.RANDOMS]:
            view = self._get_view_albums([selected_id], [], storage_type)
        elif selected_id == Type.WEB:
            view = self._get_view_albums([selected_id], [], StorageType.SAVED)
        elif selected_id == Type.YEARS:
            view = self._get_view_albums_decades(storage_type)
        elif selected_id == Type.GENRES:
            view = self._get_view_genres(storage_type)
        elif selected_id == Type.ARTISTS:
            view = self._get_view_artists_rounded(storage_type)
        elif selected_id == Type.ALL:
            view = self._get_view_albums([selected_id], [], storage_type)
        elif selected_id == Type.COMPILATIONS:
            view = self._get_view_albums([selected_id], [], storage_type)
        if view is not None and view not in self._stack.get_children():
            view.show()
            self._stack.add(view)
        if view is not None:
            self._stack.set_visible_child(view)
            if not focus_set:
                self.set_focused_view(view)
        emit_signal(self, "can-go-back-changed", self.can_go_back)

    def __on_sidebar_populated(self, selection_list):
        """
            @param selection_list as SelectionList
        """
        if App().settings.get_value("save-state"):
            self._stack.load_history()
            emit_signal(self, "can-go-back-changed", self.can_go_back)
        else:
            startup_id = App().settings.get_value("startup-id").get_int32()
            if startup_id == -1:
                selection_list.select_first()
            else:
                selection_list.select_ids([startup_id], True)

    def __on_left_list_activated(self, listbox, row):
        """
            Update view based on selected object
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        Logger.debug("Container::__on_left_list_activated()")
        selected_ids = self.left_list.selected_ids
        view = None
        storage_type = get_default_storage_type()
        if self.left_list.mask & SelectionListMask.GENRES:
            if not App().window.folded:
                view = self._get_view_albums(selected_ids, [], storage_type)
            self._show_artists_list(self.right_list, selected_ids)
            self._show_right_list()
        else:
            view = self._get_view_artists([], selected_ids, storage_type)
            self.set_focused_view(view)
            self.sub_widget.set_visible_child(self.grid_view)
        if view is not None:
            view.show()
            self._stack.add(view)
            self._stack.set_visible_child(view)

    def __on_right_list_activated(self, listbox, row):
        """
            Update view based on selected object
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        storage_type = get_default_storage_type()
        genre_ids = self.left_list.selected_ids
        artist_ids = self.right_list.selected_ids
        view = self._get_view_artists(genre_ids, artist_ids, storage_type)
        view.show()
        self._stack.add(view)
        self._stack.set_visible_child(view)
        self.set_focused_view(view)
        self.sub_widget.set_visible_child(self.grid_view)

    def __on_list_populated(self, selection_list):
        """
            Select pending ids
            @param selection_list as SelectionList
        """
        selection_list.select_pending_ids()

    def __on_set_sidebar_id(self, stack, sidebar_id):
        """
            Set sidebar id on container
            @param stack as ContainerStack
            @param sidebar_id as int
        """
        self._sidebar.select_ids([sidebar_id], False)

    def __on_set_selection_ids(self, stack, selection_ids):
        """
            Set sidebar id and left/right list ids
            @param stack as ContainerStack
            @param selection_ids as {"left": [int], "right": [int])
        """
        # Restore left list
        if selection_ids["left"]:
            if self.left_list.selected_ids != selection_ids["left"]:
                self.left_list.show()
                if self.left_list.count == 0:
                    self.left_list.set_selection_pending_ids(
                        selection_ids["left"])
                    if App().window.container.sidebar.selected_ids[0] ==\
                            Type.GENRES_LIST:
                        self._show_genres_list(self.left_list)
                    else:
                        self._show_artists_list(self.left_list)
                else:
                    self.left_list.select_ids(selection_ids["left"], False)
                    self.left_list.show()
        else:
            self.left_list.hide()
            self.left_list.clear()
        # Restore right list
        if selection_ids["right"]:
            if self.right_list.selected_ids != selection_ids["right"]:
                self.right_list.set_selection_pending_ids(
                                        selection_ids["right"])
                self._show_artists_list(self.right_list,
                                        selection_ids["left"])
                self._show_right_list()
        else:
            self._hide_right_list()
Beispiel #23
0
class ListsContainer:
    """
        Selections lists management for main view
    """
    def __init__(self):
        """
            Init container
        """
        self._list_one = self._list_two = None

    def update_list_one(self, update=False):
        """
            Update list one
            @param update as bool
        """
        if self._list_one.get_visible():
            sidebar_content = App().settings.get_enum("sidebar-content")
            if sidebar_content == SidebarContent.GENRES:
                self.__update_list_genres(self._list_one, update)
            elif sidebar_content == SidebarContent.ARTISTS:
                self.__update_list_artists(self._list_one, [Type.ALL], update)
            else:
                self.__update_list_artists(self._list_one, None, update)

    def update_list_two(self, update=False):
        """
            Update list two
            @param update as bool
        """
        if self._list_one.get_visible():
            sidebar_content = App().settings.get_enum("sidebar-content")
            ids = self._list_one.selected_ids
            if ids and ids[0] in [Type.PLAYLISTS, Type.YEARS]:
                self.__update_list_playlists(update, ids[0])
            elif sidebar_content == SidebarContent.GENRES and ids:
                self.__update_list_artists(self._list_two, ids, update)

    def show_lists(self, list_one_ids, list_two_ids):
        """
            Show list one and two
            @param list_one_ids as [int]
            @param list_two_ids as [int]
        """
        def select_list_two(selection_list, list_two_ids):
            self._list_two.select_ids(list_two_ids)
            self._list_two.disconnect_by_func(select_list_two)

        if list_two_ids:
            # Select genres on list one
            self._list_two.connect("populated", select_list_two, list_two_ids)
            self._list_one.select_ids(list_one_ids)
        else:
            self._list_one.select_ids(list_one_ids)

    @property
    def list_one(self):
        """
            Get first SelectionList
            @return SelectionList
        """
        return self._list_one

    @property
    def list_two(self):
        """
            Get second SelectionList
            @return SelectionList
        """
        return self._list_two

##############
# PROTECTED  #
##############

    def _setup_lists(self):
        """
            Add and setup list one and list two
        """
        self._list_one = SelectionList(SelectionListMask.LIST_ONE)
        self._list_two = SelectionList(SelectionListMask.LIST_TWO)
        self._list_one.listbox.connect("row-activated",
                                       self.__on_list_one_activated)
        self._list_two.listbox.connect("row-activated",
                                       self.__on_list_two_activated)
        self._list_one.connect("populated", self.__on_list_one_populated)
        self._list_one.connect("pass-focus", self.__on_pass_focus)
        self._list_two.connect("pass-focus", self.__on_pass_focus)
        self._list_two.connect("map", self.__on_list_two_mapped)
        self._paned_two.add1(self._list_two)
        self._paned_one.add1(self._list_one)
        App().window.add_paned(self._paned_one, self._list_one)
        App().window.add_paned(self._paned_two, self._list_two)
        if App().window.is_adaptive:
            self._list_one.show()
            App().window.update_layout(True)

    def _restore_state(self):
        """
            Restore list state
        """
        def select_list_two(selection_list, ids):
            # For some reasons, we need to delay this
            # If list two is short, we may receive list two selected-item
            # signal before list one
            GLib.idle_add(self._list_two.select_ids, ids)
            self._list_two.disconnect_by_func(select_list_two)

        try:
            state_one_ids = App().settings.get_value("state-one-ids")
            state_two_ids = App().settings.get_value("state-two-ids")
            state_three_ids = App().settings.get_value("state-three-ids")
            # Empty because we do not have any genre set
            if not state_one_ids:
                state_one_ids = state_two_ids
                state_two_ids = state_three_ids
            if state_one_ids:
                self._list_one.select_ids(state_one_ids)
                # If list two not available, directly show view
                sidebar_content = App().settings.get_enum("sidebar-content")
                if state_two_ids and (App().window.is_adaptive
                                      or sidebar_content
                                      == SidebarContent.DEFAULT):
                    self.show_view(state_one_ids, state_two_ids)
                # Wait for list to be populated and select item
                elif state_two_ids and not state_three_ids:
                    self._list_two.connect("populated", select_list_two,
                                           state_two_ids)

                if state_three_ids:
                    album = Album(state_three_ids[0], state_one_ids,
                                  state_two_ids)
                    self.show_view([Type.ALBUM], album)
            elif not App().window.is_adaptive:
                self._list_one.select_first()
        except Exception as e:
            Logger.error("ListsContainer::_restore_state(): %s", e)

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

    def __update_list_playlists(self, update, type):
        """
            Setup list for playlists
            @param update as bool
            @param type as int
        """
        self._list_two.mark_as(SelectionListMask.PLAYLISTS)
        if type == Type.PLAYLISTS:
            items = self._list_two.get_playlist_headers()
            items += App().playlists.get()
        else:
            (years, unknown) = App().albums.get_years()
            items = [(year, str(year), str(year)) for year in sorted(years)]
            if unknown:
                items.insert(0, (Type.NONE, _("Unknown"), ""))
        if update:
            self._list_two.update_values(items)
        else:
            self._list_two.populate(items)

    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
        """
        def load():
            genres = App().genres.get()
            return genres

        def setup(genres):
            selection_list.mark_as(SelectionListMask.GENRES)
            items = selection_list.get_headers(selection_list.mask)
            items += genres
            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
        """
        def load():
            if genre_ids is None:
                compilations = App().albums.get_compilation_ids([])
                artists = []
            elif App().settings.get_value("show-performers"):
                artists = App().artists.get_all(genre_ids)
                compilations = App().albums.get_compilation_ids(genre_ids)
            else:
                artists = App().artists.get(genre_ids)
                compilations = App().albums.get_compilation_ids(genre_ids)
            return (artists, compilations)

        def setup(artists, compilations):
            mask = SelectionListMask.ARTISTS
            if compilations:
                mask |= SelectionListMask.COMPILATIONS
            selection_list.mark_as(mask)
            items = selection_list.get_headers(selection_list.mask)
            items += artists
            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 __on_list_one_activated(self, listbox, row):
        """
            Update view based on selected object
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        Logger.debug("Container::__on_list_one_activated()")
        self._stack.destroy_children()
        if App().window.is_adaptive:
            App().window.emit("can-go-back-changed", True)
        else:
            App().window.emit("show-can-go-back", False)
            App().window.emit("can-go-back-changed", False)
        view = None
        selected_ids = self._list_one.selected_ids
        if not selected_ids:
            return
        # Update lists
        if selected_ids[0] in [Type.PLAYLISTS, Type.YEARS] and\
                self._list_one.mask & SelectionListMask.GENRES:
            self.__update_list_playlists(False, selected_ids[0])
            self._list_two.show()
        elif (selected_ids[0] > 0 or selected_ids[0] == Type.ALL) and\
                self._list_one.mask & SelectionListMask.GENRES:
            self.__update_list_artists(self._list_two, selected_ids, False)
            self._list_two.show()
        else:
            self._list_two.hide()
        # Update view
        if selected_ids[0] == Type.PLAYLISTS:
            view = self._get_view_playlists()
        elif selected_ids[0] == Type.CURRENT:
            view = self.get_view_current()
        elif selected_ids[0] == Type.SEARCH:
            view = self.get_view_search()
        elif selected_ids[0] in [
                Type.POPULARS, Type.LOVED, Type.RECENTS, Type.NEVER,
                Type.RANDOMS, Type.WEB
        ]:
            view = self._get_view_albums(selected_ids, [])
        elif selected_ids[0] == Type.RADIOS:
            view = self._get_view_radios()
        elif selected_ids[0] == Type.YEARS:
            view = self._get_view_albums_decades()
        elif selected_ids[0] == Type.GENRES:
            view = self._get_view_genres()
        elif selected_ids[0] == Type.ARTISTS:
            view = self._get_view_artists_rounded(False)
        elif self._list_one.mask & SelectionListMask.ARTISTS:
            if selected_ids[0] == Type.ALL:
                view = self._get_view_albums(selected_ids, [])
            elif selected_ids[0] == Type.COMPILATIONS:
                view = self._get_view_albums([], selected_ids)
            else:
                view = self._get_view_artists([], selected_ids)
        elif not App().window.is_adaptive:
            view = self._get_view_albums(selected_ids, [])
        if view is not None and view not in self._stack.get_children():
            self._stack.add(view)
        # If we are in paned stack mode, show list two if wanted
        if App().window.is_adaptive\
                and self._list_two.is_visible()\
                and (
                    selected_ids[0] >= 0 or
                    selected_ids[0] == Type.ALL):
            self._stack.set_visible_child(self._list_two)
        elif view is not None:
            self._stack.set_visible_child(view)

    def __on_list_one_populated(self, selection_list):
        """
            @param selection_list as SelectionList
        """
        pass

    def __on_list_two_activated(self, listbox, row):
        """
            Update view based on selected object
            @param listbox as Gtk.ListBox
            @param row as Gtk.ListBoxRow
        """
        Logger.debug("Container::__on_list_two_activated()")
        self._stack.destroy_children()
        if not App().window.is_adaptive:
            App().window.emit("show-can-go-back", False)
            App().window.emit("can-go-back-changed", False)
        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:
            view = self._get_view_playlists(selected_ids)
        elif genre_ids[0] == Type.YEARS:
            view = self._get_view_albums_years(selected_ids)
        elif selected_ids[0] == Type.COMPILATIONS:
            view = self._get_view_albums(genre_ids, selected_ids)
        else:
            view = self._get_view_artists(genre_ids, selected_ids)
        self._stack.add(view)
        self._stack.set_visible_child(view)

    def __on_pass_focus(self, selection_list):
        """
            Pass focus to other list
            @param selection_list as SelectionList
        """
        if selection_list == self._list_one:
            if self._list_two.is_visible():
                self._list_two.grab_focus()
        else:
            self._list_one.grab_focus()

    def __on_list_two_mapped(self, widget):
        """
            Force paned width, see ignore in container.py
        """
        position = App().settings.get_value("paned-listview-width").get_int32()
        GLib.timeout_add(100, self._paned_two.set_position, position)
Beispiel #24
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())
Beispiel #25
0
class Container:
    def __init__(self):

        # 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 = 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):
        if not self._progress.is_visible():
            if force or Objects.tracks.is_empty():
                self._scanner.update(False)
            elif Objects.settings.get_value('startup-scan') or\
                 Objects.settings.get_value('force-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):
        old_view = self._stack.get_visible_child()
        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, ())
        # Keep previous view,
        if isinstance(old_view, PlaylistManageView):
            old_view.destroy()

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

    def show_playlist_editor(self, playlist_name):
        old_view = self._stack.get_visible_child()
        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, ())
        # Keep previous view,
        if isinstance(old_view, PlaylistEditView):
            old_view.destroy()

    """
        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):
        # We wait as selection list is threaded,
        # we don't want to insert item before populated
        if self._list_one_restore is None:
            start_new_thread(self._scanner.add, (files, ))
        else:
            GLib.timeout_add(250, self.load_external, 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):
        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()
        view.hide()
        GLib.timeout_add(2000, view.destroy)

############
# 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.HPaned()
        self._paned_list_view = Gtk.HPaned()
        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()

        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)
            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("add-finished", self._play_tracks)

    """
        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:
                start_new_thread(self._setup_list_genres,
                                 (self._list_one, update))
            else:
                start_new_thread(self._setup_list_artists,
                                 (self._list_one, Navigation.ALL, update))

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

    def _update_list_two(self, updater):
        if self._show_genres:
            object_id = self._list_one.get_selected_id()
            if object_id == Navigation.PLAYLISTS:
                start_new_thread(self._setup_list_playlists, (True, ))
            elif isinstance(updater, CollectionScanner):
                start_new_thread(self._setup_list_artists,
                                 (self._list_two, object_id, True))

    """
        Return list one headers
    """

    def _get_headers(self):
        items = []
        items.append((Navigation.POPULARS, _("Popular 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:
            GLib.idle_add(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 = 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 len(Objects.albums.get_compilations(genre_id, sql)) > 0:
            items.append((Navigation.COMPILATIONS, _("Compilations")))

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

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

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

    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 == Navigation.POPULARS:
            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 is None:
                self._update_view_albums(object_id, None)

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

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

    """
        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 is not None:
            self._list_two.select_id(self._list_two_restore)
            self._list_two_restore = None

    """
        Play tracks as user playlist
        @param scanner as collection scanner
        @param outdb as bool (tracks not present in db)
    """

    def _play_tracks(self, scanner, outdb):
        ids = scanner.get_added()
        if ids:
            if not Objects.player.is_party():
                Objects.player.set_user_playlist(ids, ids[0])
            if outdb:
                Objects.settings.set_value('force-scan',
                                           GLib.Variant('b', True))
            Objects.player.load(ids[0])

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

    def _on_scan_finished(self, scanner):
        Objects.settings.set_value('force-scan', GLib.Variant('b', False))
        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())
Beispiel #26
0
class Container:
    def __init__(self):
        
        # 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 = 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):
        if not self._progress.is_visible():
            if force or Objects.tracks.is_empty():
                self._scanner.update(False)
            elif Objects.settings.get_value('startup-scan') or\
                 Objects.settings.get_value('force-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):
        old_view = self._stack.get_visible_child()
        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, ())
        # Keep previous view, 
        if isinstance(old_view, PlaylistManageView):
            old_view.destroy()

    """
        Show playlist editor for playlist
        Current view stay present in ViewContainer
        @param playlist name as str
    """
    def show_playlist_editor(self, playlist_name):
        old_view = self._stack.get_visible_child()
        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, ())
        # Keep previous view, 
        if isinstance(old_view, PlaylistEditView):
            old_view.destroy() 

    """
        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):
        # We wait as selection list is threaded,
        # we don't want to insert item before populated
        if self._list_one_restore is None:
            start_new_thread(self._scanner.add, (files,))
        else:
            GLib.timeout_add(250, self.load_external, 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):
        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()
        view.hide()
        GLib.timeout_add(2000, view.destroy)

############
# 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.HPaned()
        self._paned_list_view = Gtk.HPaned()
        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()

        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)
            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("add-finished", self._play_tracks)

    """
        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:
                start_new_thread(self._setup_list_genres,
                                 (self._list_one, update))
            else:
                start_new_thread(self._setup_list_artists,
                                 (self._list_one, Navigation.ALL, update))

    """
        Update list two
        @param updater as GObject
    """
    def _update_list_two(self, updater):
        if self._show_genres:
            object_id = self._list_one.get_selected_id()
            if object_id == Navigation.PLAYLISTS:
                start_new_thread(self._setup_list_playlists, (True,))
            elif isinstance(updater, CollectionScanner):
                start_new_thread(self._setup_list_artists,
                                 (self._list_two, object_id, True))

    """
        Return list one headers
    """
    def _get_headers(self):
        items = []
        items.append((Navigation.POPULARS, _("Popular 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:
            GLib.idle_add(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 = 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 len(Objects.albums.get_compilations(genre_id, sql)) > 0:
            items.append((Navigation.COMPILATIONS, _("Compilations")))

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

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

    """
        Setup list for playlists
        @param update as bool
    """
    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 == Navigation.POPULARS:
            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 is None:
                self._update_view_albums(object_id, None)

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

    """
        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 is not None:
            self._list_two.select_id(self._list_two_restore)
            self._list_two_restore = None

    """
        Play tracks as user playlist
        @param scanner as collection scanner
        @param outdb as bool (tracks not present in db)
    """
    def _play_tracks(self, scanner, outdb):
        ids = scanner.get_added()
        if ids:
            if not Objects.player.is_party():
                Objects.player.set_user_playlist(ids, ids[0])
            if outdb:
                Objects.settings.set_value('force-scan',
                                           GLib.Variant('b', True))
            Objects.player.load(ids[0])

    """
        Mark force scan as False, update lists
        @param scanner as CollectionScanner
    """
    def _on_scan_finished(self, scanner):
        Objects.settings.set_value('force-scan',
                                   GLib.Variant('b', False))
        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())
Beispiel #27
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)
Beispiel #28
0
class Window(Gtk.ApplicationWindow):

	"""
		Init window objects
	"""	
	def __init__(self, app):
		Gtk.ApplicationWindow.__init__(self,
					       application=app,
					       title=_("Lollypop"))

		self._timeout = None
		self._scanner = CollectionScanner()
		self._scanner.connect("scan-finished", self._setup_list_one, True)

		self._setup_window()				
		self._setup_view()

		self._setup_media_keys()

		party_settings = Objects["settings"].get_value('party-ids')
		ids = []
		for setting in party_settings:
			if isinstance(setting, int):
				ids.append(setting)	
		Objects["player"].set_party_ids(ids)
		self.connect("destroy", self._on_destroyed_window)

	"""
		Run collection update if needed
	"""	
	def setup_view(self):
		if Objects["tracks"].is_empty():
			self._scanner.update(self._progress, False)
			return
		elif Objects["settings"].get_value('startup-scan'):
			self._scanner.update(self._progress, True)
			
		self._setup_list_one()
		self._update_view_albums(POPULARS)

	"""
		Update music database
	"""
	def update_db(self):
		self._list_one.widget.hide()
		self._list_two.widget.hide()

		old_view = self._stack.get_visible_child()		
		self._loading = True
		view = LoadingView()
		self._stack.add(view)
		self._stack.set_visible_child(view)
		self._scanner.update(self._progress, False)
		if old_view:
			self._stack.remove(old_view)
			old_view.remove_signals()


	"""
		Update view class
		@param bool
	"""
	def update_view_class(self, dark):
		current_view = self._stack.get_visible_child()
		if dark:
			current_view.get_style_context().add_class('black')
		else:
			current_view.get_style_context().remove_class('black')

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

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

	"""
		Do key grabbing
	"""
	def _grab_media_player_keys(self):
		try:
			self._proxy.call_sync('GrabMediaPlayerKeys',
								 GLib.Variant('(su)', ('Lollypop', 0)),
								 Gio.DBusCallFlags.NONE,
								 -1,
								 None)
		except GLib.GError:
			# We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do player actions in response to media key pressed
	"""
	def _handle_media_keys(self, proxy, sender, signal, parameters):
		if signal != 'MediaPlayerKeyPressed':
			print('Received an unexpected signal \'%s\' from media player'.format(signal))
			return
		response = parameters.get_child_value(1).get_string()
		if 'Play' in response:
			Objects["player"].play_pause()
		elif 'Stop' in response:
			Objects["player"].stop()
		elif 'Next' in response:
			Objects["player"].next()
		elif 'Previous' in response:
			Objects["player"].prev()
	
	"""
		Setup window icon, position and size, callback for updating this values
	"""
	def _setup_window(self):
		self.set_icon_name('lollypop')
		size_setting = Objects["settings"].get_value('window-size')
		if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
			self.resize(size_setting[0], size_setting[1])
		else:
			self.set_size_request(800, 600)
		position_setting = Objects["settings"].get_value('window-position')
		if len(position_setting) == 2 \
			and isinstance(position_setting[0], int) \
			and isinstance(position_setting[1], int):
			self.move(position_setting[0], position_setting[1])

		if Objects["settings"].get_value('window-maximized'):
			self.maximize()

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

	"""
		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.HPaned()
		self._paned_list_view = Gtk.HPaned()
		vgrid = Gtk.Grid()
		vgrid.set_orientation(Gtk.Orientation.VERTICAL)
	
		self._toolbar = Toolbar()
		self._toolbar.header_bar.show()
		self._toolbar.get_view_genres_btn().connect("toggled", self._setup_list_one)

		self._list_one = SelectionList("Genre")
		self._list_two = SelectionList("Artist")
		self._list_one_signal = None
		self._list_two_signal = None
		
		self._loading = True
		loading_view = LoadingView()

		self._stack = Gtk.Stack()
		self._stack.add(loading_view)
		self._stack.set_visible_child(loading_view)
		self._stack.set_transition_duration(500)
		self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
		self._stack.show()

		self._progress = Gtk.ProgressBar()

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

		DESKTOP = environ.get("XDG_CURRENT_DESKTOP")
		if DESKTOP and ("GNOME" in DESKTOP or "Pantheon" in DESKTOP):
			self.set_titlebar(self._toolbar.header_bar)
			self._toolbar.header_bar.set_show_close_button(True)
			self.add(self._paned_main_list)
		else:
			hgrid = Gtk.Grid()
			hgrid.set_orientation(Gtk.Orientation.VERTICAL)
			hgrid.add(self._toolbar.header_bar)
			hgrid.add(self._paned_main_list)
			hgrid.show()
			self.add(hgrid)

		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()
		self.show()

	"""
		Init the filter list
		@param widget as unused
	"""
	def _init_main_list(self, widget):
		if self._list_one.widget.is_visible():
			self._update_genres()
		else:
			self._init_genres()
	"""
		Init list with genres or artist
		If update, only update list content
		@param obj as unused, bool
	"""
	def _setup_list_one(self, obj = None, update = None):
		if self._list_one_signal:
			self._list_one.disconnect(self._list_one_signal)
		active = self._toolbar.get_view_genres_btn().get_active()
		if active:
			items = Objects["genres"].get_ids()
		else:
			self._list_two.widget.hide()
			items = Objects["artists"].get_ids(ALL)
			if len(Objects["albums"].get_compilations(ALL)) > 0:
				items.insert(0, (COMPILATIONS, _("Compilations")))

		items.insert(0, (ALL, _("All artists")))
		items.insert(0, (POPULARS, _("Popular albums")))

		if update:
			self._list_one.update(items, not active)
		else:
			self._list_one.populate(items, not active)		

		# If was empty
		if not self._list_one_signal:
			self._list_one.select_first()

		if self._loading:
			self._stack.get_visible_child().hide()
			self._list_one.select_first()
			self._update_view_albums(POPULARS)
			self._loading = False

		self._list_one.widget.show()
		if active:
			self._list_one_signal = self._list_one.connect('item-selected', self._setup_list_two)
		else:
			self._list_one_signal = self._list_one.connect('item-selected', self._update_view_artists, None)

	"""
		Init list two with artist based on genre
		@param obj as unused, genre id as int
	"""
	def _setup_list_two(self, obj, genre_id):
		if self._list_two_signal:
			self._list_two.disconnect(self._list_two_signal)

		if genre_id == POPULARS:
			self._list_two.widget.hide()
			self._list_two_signal = None
		else:
			
			values = Objects["artists"].get_ids(genre_id)
			if len(Objects["albums"].get_compilations(genre_id)) > 0:
				values.insert(0, (COMPILATIONS, _("Compilations")))
			self._list_two.populate(values, True)
			self._list_two.widget.show()
			self._list_two_signal = self._list_two.connect('item-selected', self._update_view_artists, genre_id)
		self._update_view_albums(genre_id)
		
	"""
		Update artist view
		@param artist id as int, genre id as int
	"""
	def _update_view_artists(self, obj, artist_id, genre_id):
		if artist_id == ALL or artist_id == POPULARS:
			self._update_view_albums(artist_id)
		else:
			old_view = self._stack.get_visible_child()
			view = ArtistView(artist_id, genre_id, False)
			self._stack.add(view)
			start_new_thread(view.populate, ())
			self._stack.set_visible_child(view)
			if old_view:
				self._stack.remove(old_view)
				old_view.remove_signals()

	"""
		Update albums view
		@param genre id as int
	"""
	def _update_view_albums(self, genre_id):
		old_view = self._stack.get_visible_child()
		view = AlbumView(genre_id)
		self._stack.add(view)
		start_new_thread(view.populate, ())
		self._stack.set_visible_child(view)
		if old_view:
			self._stack.remove(old_view)
			old_view.remove_signals()

	"""
		Delay event
		@param: widget as Gtk.Window
		@param: event as Gtk.Event
	"""		
	def _on_configure_event(self, widget, event):
		if self._timeout:
			GLib.source_remove(self._timeout)
		self._timeout = GLib.timeout_add(500, self._save_size_position, widget)
	
	"""
		Save window state, update current view content size
		@param: widget as Gtk.Window
	"""
	def _save_size_position(self, widget):
		self._timeout = None
		size = widget.get_size()
		Objects["settings"].set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))
		position = widget.get_position()
		Objects["settings"].set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))

	"""
		Save maximised state
	"""
	def _on_window_state_event(self, widget, event):
		Objects["settings"].set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

	"""
		Save paned widget width
		@param widget as unused, data as unused
	"""	
	def _on_destroyed_window(self, widget):
		Objects["settings"].set_value("paned-mainlist-width", GLib.Variant('i', self._paned_main_list.get_position()))
		Objects["settings"].set_value("paned-listview-width", GLib.Variant('i', self._paned_list_view.get_position()))
Beispiel #29
0
class Window(Gtk.ApplicationWindow):

	"""
		Init window objects
	"""
	def __init__(self, app, db, player):
		Gtk.ApplicationWindow.__init__(self,
					       application=app,
					       title=_("Lollypop"))
		
		self._db = db
		self._player = player
		self._scanner = CollectionScanner()
		self._settings = Gio.Settings.new('org.gnome.Lollypop')

		self._artist_signal_id = 0

		self._setup_window()				
		self._setup_view()
		self._setup_media_keys()

		party_settings = self._settings.get_value('party-ids')
		ids = []
		for setting in party_settings:
			if isinstance(setting, int):
				ids.append(setting)	
		self._player.set_party_ids(ids)
		
		self.connect("map-event", self._on_mapped_window)



	def edit_party(self):
		builder = Gtk.Builder()
		builder.add_from_resource('/org/gnome/Lollypop/PartyDialog.ui')
		self._party_dialog = builder.get_object('party_dialog')
		self._party_dialog.set_transient_for(self)
		self._party_dialog.set_title(_("Select what will be available in party mode"))
		party_button = builder.get_object('button1')
		party_button.connect("clicked", self._edit_party_close)
		scrolled = builder.get_object('scrolledwindow1')
		genres = self._db.get_all_genres()
		genres.insert(0, (-1, "Populars"))
		self._party_grid = Gtk.Grid()
		self._party_grid.set_orientation(Gtk.Orientation.VERTICAL)
		self._party_grid.set_property("column-spacing", 10)
		ids = self._player.get_party_ids()
		i = 0
		x = 0
		for genre_id, genre in genres:
			label = Gtk.Label()
			label.set_text(genre)
			switch = Gtk.Switch()
			if genre_id in ids:
				switch.set_state(True)
			switch.connect("state-set", self._party_switch_state, genre_id)
			self._party_grid.attach(label, x, i, 1, 1)
			self._party_grid.attach(switch, x+1, i, 1, 1)
			if x == 0:
				x += 2
			else:
				i += 1
				x = 0
		scrolled.add(self._party_grid)
		self._party_dialog.show_all()

	"""
		Update music database
		Empty database if reinit True
	"""
	def update_db(self, reinit):
		if reinit:
			self._player.stop()
			self._player.clear_albums()
			self._toolbar.update_toolbar(None, None)
			self._db.reset()
		self._list_genres.widget.hide()
		self._list_artists.widget.hide()
		self._box.remove(self._view)
		self._view = LoadingView()
		self._box.add(self._view)
		self._scanner.update(self._update_genres)

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

	"""
		Update party ids when use change a switch in dialog
	"""
	def _party_switch_state(self, widget, state, genre_id):
		ids = self._player.get_party_ids()
		if state:
			try:
				ids.append(genre_id)
			except:
				pass
		else:
			try:
				ids.remove(genre_id)
			except:
				pass
		self._player.set_party_ids(ids)
		self._settings.set_value('party-ids',  GLib.Variant('ai', ids))
		

	"""
		Close edit party dialog
	"""
	def _edit_party_close(self, widget):
		self._party_dialog.hide()
		self._party_dialog.destroy()

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

	"""
		Do key grabbing
	"""
	def _grab_media_player_keys(self):
		try:
			self._proxy.call_sync('GrabMediaPlayerKeys',
								 GLib.Variant('(su)', ('Lollypop', 0)),
								 Gio.DBusCallFlags.NONE,
								 -1,
								 None)
		except GLib.GError:
			# We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do player actions in response to media key pressed
	"""
	def _handle_media_keys(self, proxy, sender, signal, parameters):
		if signal != 'MediaPlayerKeyPressed':
			print('Received an unexpected signal \'%s\' from media player'.format(signal))
			return
		response = parameters.get_child_value(1).get_string()
		if 'Play' in response:
			self._player.play_pause()
		elif 'Stop' in response:
			self._player.stop()
		elif 'Next' in response:
			self._player.next()
		elif 'Previous' in response:
			self._player.prev()
	
	"""
		Setup window icon, position and size, callback for updating this values
	"""
	def _setup_window(self):
		self.set_size_request(200, 100)
		self.set_icon_name('lollypop')
		size_setting = self._settings.get_value('window-size')
		if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
			self.resize(size_setting[0], size_setting[1])

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

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

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

	"""
		Setup window main view:
			- genre list
			- artist list
			- main view as artist view or album view
	"""
	def _setup_view(self):
		self._box = Gtk.Grid()
		self._toolbar = Toolbar(self._db, self._player)
		self.set_titlebar(self._toolbar.header_bar)
		self._toolbar.header_bar.show()
		self._toolbar.get_infobox().connect("button-press-event", self._show_current_album)

		self._list_genres = SelectionList("Genre", 150)
		self._list_artists = SelectionList("Artist", 200)
		
		self._view = LoadingView()

		separator = Gtk.Separator()
		separator.show()
		self._box.add(self._list_genres.widget)
		self._box.add(separator)
		self._box.add(self._list_artists.widget)
		self._box.add(self._view)
		self.add(self._box)
		self._box.show()
		self.show()
	
	"""
		Run collection update on mapped window
		Pass _update_genre() as collection scanned callback
	"""	
	def _on_mapped_window(self, obj, data):
		if self._db.is_empty():
			self._scanner.update(self._update_genres)
		else:
			genres = self._db.get_all_genres()
			self._update_genres(genres)
		
	"""
		Update genres list with genres
	"""
	def _update_genres(self, genres):
		genres.insert(0, (-1, _("All genres")))
		genres.insert(0, (-2, _("Populars albums")))
		self._list_genres.populate(genres)
		self._list_genres.connect('item-selected', self._update_artists)
		self._list_genres.widget.show()
		self._list_genres.select_first()

	"""
		Update artist list for genre_id
	"""
	def _update_artists(self, obj, genre_id):
		if self._artist_signal_id:
			self._list_artists.disconnect(self._artist_signal_id)
		if genre_id == -1:
			self._list_artists.populate(self._db.get_all_artists(), True)
			self._update_view_albums(self, -1)
			self._list_artists.widget.show()
		elif genre_id == -2:
			self._update_view_populars_albums()
			self._list_artists.widget.hide()
		else:
			self._list_artists.populate(self._db.get_artists_by_genre_id(genre_id), True)
			self._update_view_albums(self, genre_id)
			self._list_artists.widget.show()
		self._artist_signal_id = self._list_artists.connect('item-selected', self._update_view_artist)
		self._genre_id = genre_id

	"""
		Update artist view for artist_id
	"""
	def _update_view_artist(self, obj, artist_id):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = ArtistView(self._db, self._player, self._genre_id, artist_id)
		self._box.add(self._view)
		self._view.populate()
	
	"""
		Update albums view with populars albums
	"""
	def _update_view_populars_albums(self):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = AlbumView(self._db, self._player, None)
		self._box.add(self._view)
		self._view.populate_popular()
	"""
		Update albums view for genre_id
	"""
	def _update_view_albums(self, obj, genre_id):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = AlbumView(self._db, self._player, genre_id)
		self._box.add(self._view)
		self._view.populate()
	
	"""
		Save new window size/position
	"""		
	def _on_configure_event(self, widget, event):
		size = widget.get_size()
		self._settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

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

	"""
		Save maximised state
	"""
	def _on_window_state_event(self, widget, event):
		self._settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

	"""
		Show current album context/content
	"""
	def _show_current_album(self, obj, data):
		track_id = self._player.get_current_track_id()
		if  track_id != -1:
			self._view.current_changed(False, track_id)
Beispiel #30
0
class DeviceManagerWidget(Gtk.Bin, MtpSync):
    """
        Widget for synchronize mtp devices
    """
    __gsignals__ = {"sync-finished": (GObject.SignalFlags.RUN_FIRST, None, ())}

    def __init__(self, parent):
        """
            Init widget
            @param device as Device
            @param parent as Gtk.Widget
        """
        Gtk.Bin.__init__(self)
        MtpSync.__init__(self)
        self.__parent = parent
        self.__stop = False
        self._uri = None

        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/DeviceManagerWidget.ui")
        widget = builder.get_object("widget")
        self.__error_label = builder.get_object("error-label")
        self.__switch_albums = builder.get_object("switch_albums")
        self.__switch_albums.set_state(Lp().settings.get_value("sync-albums"))
        self.__switch_mp3 = builder.get_object("switch_mp3")
        self.__switch_normalize = builder.get_object("switch_normalize")
        if not self._check_encoder_status():
            self.__switch_mp3.set_sensitive(False)
            self.__switch_normalize.set_sensitive(False)
            self.__switch_mp3.set_tooltip_text(
                _("You need to install " + "gstreamer-plugins-ugly"))
        else:
            self.__switch_mp3.set_state(Lp().settings.get_value("convert-mp3"))
        self.__menu_items = builder.get_object("menu-items")
        self.__menu = builder.get_object("menu")

        self.__model = Gtk.ListStore(bool, str, int)

        self.__selection_list = SelectionList(False)
        self.__selection_list.connect("item-selected", self.__on_item_selected)
        widget.attach(self.__selection_list, 1, 1, 1, 1)
        self.__selection_list.set_hexpand(True)

        self.__view = builder.get_object("view")
        self.__view.set_model(self.__model)

        builder.connect_signals(self)

        self.add(widget)

        self.__infobar = builder.get_object("infobar")
        self.__infobar_label = builder.get_object("infobarlabel")

        renderer0 = Gtk.CellRendererToggle()
        renderer0.set_property("activatable", True)
        renderer0.connect("toggled", self.__on_item_toggled)
        column0 = Gtk.TreeViewColumn(" ✓", renderer0, active=0)
        column0.set_clickable(True)
        column0.connect("clicked", self.__on_column0_clicked)

        renderer1 = CellRendererAlbum()
        self.__column1 = Gtk.TreeViewColumn("", renderer1, album=2)

        renderer2 = Gtk.CellRendererText()
        renderer2.set_property("ellipsize-set", True)
        renderer2.set_property("ellipsize", Pango.EllipsizeMode.END)
        self.__column2 = Gtk.TreeViewColumn("", renderer2, markup=1)
        self.__column2.set_expand(True)

        self.__view.append_column(column0)
        self.__view.append_column(self.__column1)
        self.__view.append_column(self.__column2)

    def populate(self):
        """
            Populate playlists
            @thread safe
        """
        self.__model.clear()
        self.__stop = False
        if Lp().settings.get_value("sync-albums"):
            self.__selection_list.clear()
            self.__setup_list_artists(self.__selection_list)
            self.__column1.set_visible(True)
            self.__column2.set_title(_("Albums"))
            self.__selection_list.show()
            self.__selection_list.select_ids([Type.ALL])
        else:
            playlists = [(Type.LOVED, Lp().playlists.LOVED)]
            playlists += Lp().playlists.get()
            self.__append_playlists(playlists)
            self.__column1.set_visible(False)
            self.__column2.set_title(_("Playlists"))
            self.__selection_list.hide()

    def set_uri(self, uri):
        """
            Set uri
            @param uri as str
        """
        self._uri = uri
        d = Gio.File.new_for_uri(uri)
        try:
            if not d.query_exists():
                d.make_directory_with_parents()
        except:
            pass

    def is_syncing(self):
        """
            @return True if syncing
        """
        return self._syncing

    def sync(self):
        """
            Start synchronisation
        """
        self._syncing = True
        Lp().window.progress.add(self)
        self.__menu.set_sensitive(False)
        playlists = []
        if not Lp().settings.get_value("sync-albums"):
            self.__view.set_sensitive(False)
            for item in self.__model:
                if item[0]:
                    playlists.append(item[2])
        else:
            playlists.append(Type.NONE)

        helper = TaskHelper()
        helper.run(self._sync, playlists, self.__switch_mp3.get_active(),
                   self.__switch_normalize.get_active())

    def cancel_sync(self):
        """
            Cancel synchronisation
        """
        self._syncing = False

    def show_overlay(self, bool):
        """
            No overlay here now
        """
        pass

#######################
# PROTECTED           #
#######################

    def _update_progress(self):
        """
            Update progress bar smoothly
        """
        current = Lp().window.progress.get_fraction()
        if self._syncing:
            progress = (self._fraction - current) / 10
        else:
            progress = 0.01
        if current < self._fraction:
            Lp().window.progress.set_fraction(current + progress, self)
        if current < 1.0:
            if progress < 0.0002:
                GLib.timeout_add(500, self._update_progress)
            else:
                GLib.timeout_add(25, self._update_progress)
        else:
            GLib.timeout_add(1000, self._on_finished)

    def _pop_menu(self, button):
        """
            Popup menu for album
            @param button as Gtk.Button
            @param album id as int
        """
        parent = self.__menu_items.get_parent()
        if parent is not None:
            parent.remove(self.__menu_items)
        popover = Gtk.Popover.new(button)
        popover.set_position(Gtk.PositionType.BOTTOM)
        popover.add(self.__menu_items)
        popover.show()

    def _on_finished(self):
        """
            Emit finished signal
        """
        MtpSync._on_finished(self)
        Lp().window.progress.set_fraction(1.0, self)
        if not self.__switch_albums.get_state():
            self.__view.set_sensitive(True)
        self.__menu.set_sensitive(True)
        self.emit("sync-finished")

    def _on_errors(self):
        """
            Show information bar with error message
        """
        MtpSync._on_errors(self)
        error_text = _("Unknown error while syncing,"
                       " try to reboot your device")
        try:
            d = Gio.File.new_for_uri(self._uri)
            info = d.query_filesystem_info("filesystem::free")
            free = info.get_attribute_as_string("filesystem::free")

            if free is None or int(free) < 1024:
                error_text = _("No free space available on device")
        except Exception as e:
            print("DeviceWidget::_on_errors(): %s" % e)
        self.__error_label.set_text(error_text)
        self.__infobar.show()

    def _on_albums_state_set(self, widget, state):
        """
            Enable or disable playlist selection
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        self.__stop = True
        Lp().settings.set_value("sync-albums", GLib.Variant("b", state))
        GLib.idle_add(self.populate)

    def _on_mp3_state_set(self, widget, state):
        """
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        Lp().settings.set_value("convert-mp3", GLib.Variant("b", state))
        if not state:
            self.__switch_normalize.set_active(False)
            Lp().settings.set_value("normalize-mp3", GLib.Variant("b", False))

    def _on_normalize_state_set(self, widget, state):
        """
            Save option
            @param widget as Gtk.Switch
            @param state as bool
        """
        Lp().settings.set_value("normalize-mp3", GLib.Variant("b", state))
        if state:
            self.__switch_mp3.set_active(True)
            Lp().settings.set_value("convert-mp3", GLib.Variant("b", True))

    def _on_response(self, infobar, response_id):
        """
            Hide infobar
            @param widget as Gtk.Infobar
            @param reponse id as int
        """
        if response_id == Gtk.ResponseType.CLOSE:
            self.__infobar.hide()

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

    def __setup_list_artists(self, selection_list):
        """
            Setup list for artists
            @param list as SelectionList
            @thread safe
        """
        def load():
            artists = Lp().artists.get_local()
            compilations = Lp().albums.get_compilation_ids()
            return (artists, compilations)

        def setup(artists, compilations):
            items = []
            items.append((Type.ALL, _("Synced albums")))
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations")))
                items.append((Type.SEPARATOR, ""))
            items += artists
            selection_list.mark_as_artists(True)
            selection_list.populate(items)

        loader = Loader(target=load,
                        view=selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def __append_playlists(self, playlists, files_list=[]):
        """
            Append a playlist
            @param playlists as [(int, str)]
            @internal files_list
        """
        if playlists and not self.__stop:
            # Cache directory playlists
            if not files_list:
                try:
                    d = Gio.File.new_for_uri(self._uri)
                    infos = d.enumerate_children(
                        "standard::name,standard::type",
                        Gio.FileQueryInfoFlags.NONE, None)
                    for info in infos:
                        if info.get_file_type() != Gio.FileType.DIRECTORY:
                            f = infos.get_child(info)
                            if f.get_uri().endswith(".m3u"):
                                files_list.append(
                                    GLib.path_get_basename(f.get_path()))
                except:
                    pass
            playlist = playlists.pop(0)
            selected = playlist[1] + ".m3u" in files_list
            self.__model.append([selected, playlist[1], playlist[0]])
            GLib.idle_add(self.__append_playlists, playlists, files_list)

    def __append_albums(self, albums):
        """
            Append albums
            @param albums as [int]
        """
        if albums and not self.__stop:
            album = Album(albums.pop(0))
            synced = Lp().albums.get_synced(album.id)
            # Do not sync youtube albums
            if synced != Type.NONE:
                if album.artist_ids[0] == Type.COMPILATIONS:
                    name = GLib.markup_escape_text(album.name)
                else:
                    artists = ", ".join(album.artists)
                    name = "<b>%s</b> - %s" % (GLib.markup_escape_text(
                        artists), GLib.markup_escape_text(album.name))
                self.__model.append([synced, name, album.id])
            GLib.idle_add(self.__append_albums, albums)

    def __populate_albums_playlist(self, album_id, toggle):
        """
            Populate hidden albums playlist
            @param album_id as int
            @param toggle as bool
            @warning commit on default sql cursor needed
        """
        if Lp().settings.get_value("sync-albums"):
            Lp().albums.set_synced(album_id, toggle)

    def __on_item_selected(self, selection_list):
        """
            Show album from artist
            @param selection list as SelectionList
        """
        if not selection_list.selected_ids:
            return
        if selection_list.selected_ids[0] == Type.COMPILATIONS:
            albums = Lp().albums.get_compilation_ids()
        elif selection_list.selected_ids[0] == Type.ALL:
            albums = Lp().albums.get_synced_ids()
        else:
            albums = Lp().albums.get_ids(selection_list.selected_ids)
        self.__model.clear()
        self.__append_albums(albums)

    def __on_column0_clicked(self, column):
        """
            Select/Unselect all playlists
            @param column as Gtk.TreeViewColumn
        """
        selected = False
        for item in self.__model:
            if item[0]:
                selected = True
        for item in self.__model:
            item[0] = not selected
            self.__populate_albums_playlist(item[2], item[0])
        with SqlCursor(Lp().db) as sql:
            sql.commit()

    def __on_item_toggled(self, view, path):
        """
            When item is toggled, set model
            @param widget as cell renderer
            @param path as str representation of Gtk.TreePath
        """
        iterator = self.__model.get_iter(path)
        toggle = not self.__model.get_value(iterator, 0)
        self.__model.set_value(iterator, 0, toggle)
        album_id = self.__model.get_value(iterator, 2)
        self.__populate_albums_playlist(album_id, toggle)
        with SqlCursor(Lp().db) as sql:
            sql.commit()
Beispiel #31
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())
Beispiel #32
0
    def __init__(self, parent):
        """
            Init widget
            @param device as Device
            @param parent as Gtk.Widget
        """
        Gtk.Bin.__init__(self)
        MtpSync.__init__(self)
        self.__parent = parent
        self.__stop = False
        self._uri = None

        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/DeviceManagerWidget.ui")
        widget = builder.get_object("widget")
        self.__error_label = builder.get_object("error-label")
        self.__switch_albums = builder.get_object("switch_albums")
        self.__switch_albums.set_state(Lp().settings.get_value("sync-albums"))
        self.__switch_mp3 = builder.get_object("switch_mp3")
        self.__switch_normalize = builder.get_object("switch_normalize")
        if not self._check_encoder_status():
            self.__switch_mp3.set_sensitive(False)
            self.__switch_normalize.set_sensitive(False)
            self.__switch_mp3.set_tooltip_text(
                _("You need to install " + "gstreamer-plugins-ugly"))
        else:
            self.__switch_mp3.set_state(Lp().settings.get_value("convert-mp3"))
        self.__menu_items = builder.get_object("menu-items")
        self.__menu = builder.get_object("menu")

        self.__model = Gtk.ListStore(bool, str, int)

        self.__selection_list = SelectionList(False)
        self.__selection_list.connect("item-selected", self.__on_item_selected)
        widget.attach(self.__selection_list, 1, 1, 1, 1)
        self.__selection_list.set_hexpand(True)

        self.__view = builder.get_object("view")
        self.__view.set_model(self.__model)

        builder.connect_signals(self)

        self.add(widget)

        self.__infobar = builder.get_object("infobar")
        self.__infobar_label = builder.get_object("infobarlabel")

        renderer0 = Gtk.CellRendererToggle()
        renderer0.set_property("activatable", True)
        renderer0.connect("toggled", self.__on_item_toggled)
        column0 = Gtk.TreeViewColumn(" ✓", renderer0, active=0)
        column0.set_clickable(True)
        column0.connect("clicked", self.__on_column0_clicked)

        renderer1 = CellRendererAlbum()
        self.__column1 = Gtk.TreeViewColumn("", renderer1, album=2)

        renderer2 = Gtk.CellRendererText()
        renderer2.set_property("ellipsize-set", True)
        renderer2.set_property("ellipsize", Pango.EllipsizeMode.END)
        self.__column2 = Gtk.TreeViewColumn("", renderer2, markup=1)
        self.__column2.set_expand(True)

        self.__view.append_column(column0)
        self.__view.append_column(self.__column1)
        self.__view.append_column(self.__column2)
class DeviceView(View):
    """
        Playlist synchronisation to MTP
    """
    def exists_old_sync(uri):
        """
            True if exists an old sync on device
            @param uri as str
            @return bool
        """
        d = Gio.File.new_for_uri(uri + "/Music/lollypop/tracks")
        return d.query_exists()

    def get_files(uri):
        """
            Get files for uri
            @param uri as str
            @return [str]
        """
        files = []
        try:
            d = Gio.File.new_for_uri(uri)
            if not d.query_exists():
                d.make_directory_with_parents()
            infos = d.enumerate_children("standard::name,standard::type",
                                         Gio.FileQueryInfoFlags.NONE, None)
            for info in infos:
                if info.get_file_type() != Gio.FileType.DIRECTORY:
                    continue
                f = infos.get_child(info)
                # We look to this folder to select an already synced uri
                suburi = f.get_uri() + "/Music/unsync"
                sub = Gio.File.new_for_uri(suburi)
                if sub.query_exists():
                    files.insert(0, info.get_name())
                else:
                    files.append(info.get_name())
            infos.close(None)
        except Exception as e:
            Logger.error("DeviceManagerView::_get_files: %s: %s" % (uri, e))
            files = []
        return files

    def __init__(self, device):
        """
            Init view
            @param device as Device
        """
        View.__init__(self)
        self.__timeout_id = None
        self.__device = device
        self.__selected_ids = []
        builder = Gtk.Builder()
        builder.add_from_resource("/org/gnome/Lollypop/DeviceManagerView.ui")
        self.__memory_combo = builder.get_object("memory_combo")
        self.__syncing_btn = builder.get_object("sync_btn")
        # FIXME Wait for translation
        _("Synchronize")
        self.__syncing_btn.set_label(_("Synchronize %s") % "")
        builder.connect_signals(self)
        self.__device_widget = DeviceManagerWidget(self)
        self.__device_widget.mtp_sync.connect("sync-finished",
                                              self.__on_sync_finished)
        self.__device_widget.mtp_sync.connect("sync-errors",
                                              self.__on_sync_errors)
        self.__device_widget.show()
        self.__infobar = builder.get_object("infobar")
        self.__error_label = builder.get_object("error_label")
        self.__paned = builder.get_object("paned")
        self.__selection_list = SelectionList(SelectionListMask.LIST_ONE)
        self.__selection_list.connect("item-selected", self.__on_item_selected)
        self.__selection_list.mark_as(SelectionListMask.ARTISTS)
        self.__selection_list.show()
        self.__paned.add1(self.__selection_list)
        self.__paned.add2(builder.get_object("device_view"))
        builder.get_object("device_view").attach(self._scrolled, 0, 3, 4, 1)
        self.add(self.__paned)
        self.__paned.set_position(
            App().settings.get_value("paned-device-width").get_int32())

        self.__update_list_device()
        self.__sanitize_non_mtp()

    def populate(self, selected_ids=[]):
        """
            Populate combo box
            @param selected_ids as [int]
            @thread safe
        """
        child = self._viewport.get_child()
        self.__selected_ids = selected_ids
        if selected_ids:
            if child is not None and isinstance(child, Gtk.Label):
                child.destroy()
                self._viewport.add(self.__device_widget)
        elif child is None:
            label = Gtk.Label.new(
                _("This will remove some files on your device!"))
            label.get_style_context().add_class("lyrics-x-large")
            label.get_style_context().add_class("lyrics")
            label.set_vexpand(True)
            label.set_hexpand(True)
            label.show()
            self._viewport.add(label)
        files = DeviceView.get_files(self.__device.uri)
        if files:
            GLib.idle_add(self.__set_combo_text, files)
        else:
            GLib.idle_add(self.destroy)

    def is_syncing(self):
        """
            Check if lollypop is syncing
            @return bool
        """
        return not self.__device_widget.mtp_sync.cancellable.is_cancelled()

    @property
    def device(self):
        """
            Get device for view
            @return Device
        """
        return self.__device

    @property
    def should_destroy(self):
        return False

#######################
# PROTECTED           #
#######################

    def _on_infobar_response(self, infobar, response_id):
        """
            Hide infobar
            @param widget as Gtk.Infobar
            @param reponse id as int
        """
        if response_id == Gtk.ResponseType.CLOSE:
            self.__infobar.set_revealed(False)
            # WTF?
            GLib.timeout_add(300, self.__infobar.hide)

    def _on_destroy(self, widget):
        """
            Remove running timeout
            @param widget as Gtk.Widget
        """
        if self.__timeout_id is not None:
            GLib.source_remove(self.__timeout_id)
            self.__timeout_id = None
        View._on_destroy(self, widget)

    def _on_sync_clicked(self, widget):
        """
            Start synchronisation
            @param widget as Gtk.Button
        """
        if not self.__device_widget.mtp_sync.cancellable.is_cancelled():
            self.__device_widget.mtp_sync.cancellable.cancel()
        elif not App().window.container.progress.is_visible():
            self.__memory_combo.hide()
            self.__syncing_btn.set_label(_("Cancel synchronization"))
            self.__device_widget.sync()

    def _on_memory_combo_changed(self, combo):
        """
            Update path
            @param combo as Gtk.ComboxText
        """
        self.__timeout_id = None
        text = combo.get_active_text()
        uri = "%s%s/Music" % (self.__device.uri, text)
        self.__device_widget.set_uri(uri)
        if self.__selected_ids:
            self.__device_widget.populate(self.__selected_ids)

    def _on_map(self, widget):
        """
            Set active ids
            @param widget as Gtk.Widget
        """
        App().settings.set_value("state-one-ids", GLib.Variant("ai", []))

    def _on_unmap(self, widget):
        """
            Save paned position
            @param widget as Gtk.Widget
        """
        App().settings.set_value(
            "paned-device-width", GLib.Variant("i",
                                               self.__paned.get_position()))

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

    def __update_list_device(self):
        """
            Setup list for device
            @param list as SelectionList
            @thread safe
        """
        def load():
            artists = App().artists.get()
            compilations = App().albums.get_compilation_ids([])
            return (artists, compilations)

        def setup(artists, compilations):
            items = [(Type.ALL, _("Synced albums"), "")]
            items.append((Type.PLAYLISTS, _("Playlists"), ""))
            if compilations:
                items.append((Type.COMPILATIONS, _("Compilations"), ""))
                items.append((Type.SEPARATOR, "", ""))
            items += artists
            self.__selection_list.populate(items)

        loader = Loader(target=load,
                        view=self.__selection_list,
                        on_finished=lambda r: setup(*r))
        loader.start()

    def __sanitize_non_mtp(self):
        """
            Sanitize non MTP device by changing uri and creating a default
            folder
        """
        uri = self.__device.uri
        # Mtp device contain a virtual folder
        # For others, just go up in path
        if uri.find("mtp:") == -1:
            m = re.search("(.*)/[^/]*", uri)
            if m:
                uri = m.group(1)
        # Add / to uri if needed, some gvfs modules add one and some not
        if uri is not None and len(uri) > 1 and uri[-1:] != "/":
            uri += "/"
        self.__device.uri = uri

    def stop(self):
        pass

    def __on_sync_errors(self, mtp_sync, error):
        """
            Show information bar with error message
            @param mtp_sync as MtpSync
            @param error as str
        """
        error_text = error or _("Unknown error while syncing,"
                                " try to reboot your device")
        self.__error_label.set_text(error_text)
        self.__infobar.show()
        self.__infobar.set_revealed(True)

    def __on_sync_finished(self, device_widget):
        """
            Restore widgets state
            @param device widget as DeviceManager
        """
        self.__memory_combo.show()
        self.__syncing_btn.set_label(_("Synchronize %s") % self.__device.name)

    def __set_combo_text(self, text_list):
        """
            Set combobox text
            @param text list as [str]
        """
        # Just update device widget if already populated
        if self.__memory_combo.get_active_text() is not None:
            if self.__device_widget.mtp_sync.cancellable.is_cancelled():
                self.__device_widget.populate(self.__selected_ids)
            return
        for text in text_list:
            self.__memory_combo.append_text(text)
        self.__memory_combo.set_active(0)

    def __on_item_selected(self, selectionlist):
        """
            Update view
            @param selection_list as SelectionList
        """
        self.populate(selectionlist.selected_ids)