コード例 #1
0
ファイル: window.py プロジェクト: erwanj/lollypop
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()))
コード例 #2
0
ファイル: container.py プロジェクト: mank319/lollypop
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())
コード例 #3
0
ファイル: container.py プロジェクト: mank319/lollypop
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())