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()
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()
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
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()
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()
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)
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)
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()
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 __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()
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 __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 _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()
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)
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()
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 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()
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())
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()
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)
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())
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())
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())
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)
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()))
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)
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()
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())
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)