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) self._toolbar = Toolbar(self.get_application()) self._toolbar.show() # Only set headerbar if according DE detected or forced manually if use_csd(): self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) else: hgrid = Gtk.Grid() hgrid.set_orientation(Gtk.Orientation.VERTICAL) hgrid.add(self._toolbar) hgrid.add(self.main_widget()) hgrid.show() self.add(hgrid)
def __setup_content(self): """ Setup window content """ self.__container = Container() self.set_stack(self.container.stack) self.add_paned(self.container.paned_one, self.container.list_one) self.add_paned(self.container.paned_two, self.container.list_two) self.__container.show() self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_uri_targets() self.connect("drag-data-received", self.__on_drag_data_received)
def __setup_content(self): """ Setup window content """ vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self.__toolbar = Toolbar() self.__toolbar.show() self.__subtoolbar = Gtk.Grid() if Lp().settings.get_value("disable-csd") or is_unity(): vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd")) vgrid.add(self.__main_stack) vgrid.add(self.__subtoolbar) self.add(vgrid) self.__main_stack.add_named(self._paned_main_list, "main") self.__main_stack.set_visible_child_name("main") self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_text_targets() self.connect("drag-data-received", self.__on_drag_data_received) self.connect("drag-motion", self.__on_drag_motion) self.connect("drag-leave", self.__on_drag_leave)
def _setup_content(self): """ Setup window content """ self.set_icon_name('lollypop') self._toolbar = Toolbar(self.get_application()) self._toolbar.show() if Lp.settings.get_value('disable-csd'): vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.add(self._toolbar) vgrid.add(self.main_widget()) vgrid.show() self.add(vgrid) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget())
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 _setup_content(self): """ Setup window content """ self.set_default_icon_name('lollypop') vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self._toolbar = Toolbar(self.get_application()) self._toolbar.show() self._subtoolbar = Gtk.Grid() if Lp().settings.get_value('disable-csd') or is_unity(): vgrid.add(self._toolbar) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) vgrid.add(self._main_stack) vgrid.add(self._subtoolbar) self.add(vgrid) self._main_stack.add_named(self.main_widget(), 'main') self._main_stack.set_visible_child_name('main')
def __setup_content(self): """ Setup window content """ vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self.__toolbar = Toolbar() self.__toolbar.show() self.__subtoolbar = Gtk.Grid() if Lp().settings.get_value("disable-csd") or is_unity(): vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd")) vgrid.add(self.__main_stack) vgrid.add(self.__subtoolbar) self.add(vgrid) self.__main_stack.add_named(self._paned_main_list, "main") self.__main_stack.set_visible_child_name("main")
def setup(self): """ Setup window content """ self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__container = Container() self.__container.setup() self.__container.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.__container.widget.connect("notify::folded", self.__on_container_folded)
def _setup_content(self): self.set_icon_name('lollypop') self._toolbar = Toolbar(self.get_application()) self._toolbar.show() # Only set headerbar if according DE detected or forced manually if use_csd(): self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) else: vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.add(self._toolbar) vgrid.add(self.main_widget()) vgrid.show() self.add(vgrid)
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()
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, Container): """ Main window """ def __init__(self): """ Init window """ Container.__init__(self) self.__signal1 = None self.__signal2 = None self.__timeout = None self.__was_maximized = False Gtk.ApplicationWindow.__init__(self, application=Lp(), title="Lollypop") self.connect("hide", self.__on_hide) Lp().player.connect("current-changed", self.__on_current_changed) self.__timeout_configure = None seek_action = Gio.SimpleAction.new("seek", GLib.VariantType.new("i")) seek_action.connect("activate", self.__on_seek_action) Lp().add_action(seek_action) player_action = Gio.SimpleAction.new("shortcut", GLib.VariantType.new("s")) player_action.connect("activate", self.__on_player_action) Lp().add_action(player_action) self.__setup_global_shortcuts() self.__main_stack = Gtk.Stack() self.__main_stack.set_transition_duration(1000) self.__main_stack.set_transition_type( Gtk.StackTransitionType.CROSSFADE) self.__main_stack.show() self.__setup_content() self.setup_window() self.__setup_media_keys() self.__enabled_shortcuts = False self.enable_global_shortcuts(True) self.connect("destroy", self.__on_destroyed_window) self.connect("realize", self.__on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self.__toolbar.setup_menu(menu) def enable_global_shortcuts(self, enable): """ Enable/Disable special global shortcuts @param enable as bool """ if self.__enabled_shortcuts == enable: return self.__enabled_shortcuts = enable if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: Lp().set_accels_for_action("app.seek(10)", ["Left"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Left"]) Lp().set_accels_for_action("app.seek(-10)", ["Right"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: Lp().set_accels_for_action("app.seek(10)", ["Right"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Right"]) Lp().set_accels_for_action("app.seek(-10)", ["Left"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Left"]) Lp().set_accels_for_action("app.shortcut::play_pause", ["space", "c"]) Lp().set_accels_for_action("app.shortcut::play", ["x"]) Lp().set_accels_for_action("app.shortcut::stop", ["v"]) Lp().set_accels_for_action("app.shortcut::next", ["n"]) Lp().set_accels_for_action("app.shortcut::prev", ["p"]) Lp().set_accels_for_action("app.shortcut::loved", ["l"]) else: Lp().set_accels_for_action("app.seek(10)", [None]) Lp().set_accels_for_action("app.seek(20)", [None]) Lp().set_accels_for_action("app.seek(-10)", [None]) Lp().set_accels_for_action("app.seek(-20)", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::next", [None]) Lp().set_accels_for_action("app.shortcut::next_album", [None]) Lp().set_accels_for_action("app.shortcut::prev", [None]) Lp().set_accels_for_action("app.shortcut::loved", [None]) def setup_window(self): """ Setup window position and size, callbacks """ self.__setup_pos_size("window") if Lp().settings.get_value("window-maximized"): self.maximize() if self.__signal1 is None: self.__signal1 = self.connect("window-state-event", self.__on_window_state_event) if self.__signal2 is None: self.__signal2 = self.connect("configure-event", self.__on_configure_event) def responsive_design(self): """ Handle responsive design """ size = self.get_size() self.__toolbar.set_content_width(size[0]) self.__show_miniplayer(size[0] < WindowSize.MEDIUM) self.__show_subtoolbar(size[0] < WindowSize.MONSTER and size[0] > WindowSize.MEDIUM) def set_mini(self): """ Set mini player on/off """ if Lp().player.current_track.id is None: return was_maximized = self.is_maximized() if self.__main_stack.get_visible_child_name() == "main": if self.is_maximized(): self.unmaximize() GLib.timeout_add(100, self.__setup_pos_size, "mini") else: self.__setup_pos_size("mini") elif self.__was_maximized: self.maximize() else: self.__setup_pos_size("window") self.__was_maximized = was_maximized @property def toolbar(self): """ toolbar as Toolbar """ return self.__toolbar def do_event(self, event): """ Update overlays as internal widget may not have received the signal @param widget as Gtk.Widget @param event as Gdk.event """ if event.type == Gdk.EventType.FOCUS_CHANGE and self.view is not None: self.view.disable_overlay() Lp().player.preview.set_state(Gst.State.NULL) Gtk.ApplicationWindow.do_event(self, event) ############ # Private # ############ def __setup_global_shortcuts(self): """ Setup global shortcuts """ Lp().set_accels_for_action("app.shortcut::locked", ["<Control>l"]) Lp().set_accels_for_action("app.shortcut::filter", ["<Control>i"]) Lp().set_accels_for_action("app.shortcut::volume", ["<Alt>v"]) Lp().set_accels_for_action("app.shortcut::next_album", ["<Control>n"]) Lp().set_accels_for_action("app.shortcut::show_genres", ["<Control>g"]) Lp().set_accels_for_action("app.shortcut::hide_pane", ["<Control>h"]) Lp().set_accels_for_action("app.update_db", ["<Control>u"]) Lp().set_accels_for_action("app.settings", ["<Control>s"]) Lp().set_accels_for_action("app.fullscreen", ["F11", "F7"]) Lp().set_accels_for_action("app.mini", ["<Control>m"]) Lp().set_accels_for_action("app.about", ["F3"]) Lp().set_accels_for_action("app.shortcuts", ["F2"]) Lp().set_accels_for_action("app.help", ["F1"]) Lp().set_accels_for_action("app.quit", ["<Control>q"]) def __show_subtoolbar(self, show): """ Show/hide subtoolbar @param show as bool """ is_visible = self.__subtoolbar.is_visible() if show and not is_visible: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() mini.show() self.__subtoolbar.add(mini) self.__subtoolbar.show() elif not show and is_visible: children = self.__subtoolbar.get_children() if children: children[0].destroy() self.__subtoolbar.hide() def __show_miniplayer(self, show): """ Show/hide miniplayer @param show as bool """ mini = self.__main_stack.get_child_by_name("mini") if show: if mini is not None: if self.__timeout is not None: GLib.source_remove(self.__timeout) else: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() self.__main_stack.add_named(mini, "mini") self.__timeout = None mini.show() self.__main_stack.set_visible_child_name("mini") self.__toolbar.set_show_close_button(False) elif mini is not None and not show and self.__timeout is None: self.__main_stack.set_visible_child_name("main") self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd") and not is_unity()) self.__timeout = GLib.timeout_add(1000, mini.destroy) def __setup_pos_size(self, name): """ Set window pos and size based on name @param name as str """ size_setting = Lp().settings.get_value("%s-size" % name) if len(size_setting) == 2 and\ isinstance(size_setting[0], int) and\ isinstance(size_setting[1], int): self.resize(size_setting[0], size_setting[1]) if name == "window": self.__setup_pos(name) else: # We need position to happen after resize as previous # may be refused by window manager => mini player as bottom GLib.idle_add(self.__setup_pos, name) def __setup_pos(self, name): """ Set window position @param name as str """ position_setting = Lp().settings.get_value("%s-position" % name) 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]) def __setup_media_keys(self): """ Setup media player keys """ 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 def __grab_media_player_keys(self): """ Do key grabbing """ 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 def __handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp().player.play_pause() elif "Stop" in response: Lp().player.stop() elif "Next" in response: Lp().player.next() elif "Previous" in response: Lp().player.prev() def __setup_content(self): """ Setup window content """ self.set_default_icon_name("lollypop") vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self.__toolbar = Toolbar() self.__toolbar.show() self.__subtoolbar = Gtk.Grid() if Lp().settings.get_value("disable-csd") or is_unity(): vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd")) vgrid.add(self.__main_stack) vgrid.add(self.__subtoolbar) self.add(vgrid) self.__main_stack.add_named(self._paned_main_list, "main") self.__main_stack.set_visible_child_name("main") def __on_hide(self, window): """ Remove callbacks (we don"t want to save an invalid value on hide @param window as GtkApplicationWindow """ if self.__signal1 is not None: self.disconnect(self.__signal1) self.__signal1 = None if self.__signal2 is not None: self.disconnect(self.__signal2) self.__signal2 = None def __on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ if self.__timeout_configure: GLib.source_remove(self.__timeout_configure) self.__timeout_configure = None self.responsive_design() if not self.is_maximized(): self.__timeout_configure = GLib.timeout_add( 1000, self.__save_size_position, widget) def __save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self.__timeout_configure = None size = widget.get_size() if size[0] > WindowSize.MEDIUM: name = "window" else: name = "mini" Lp().settings.set_value("%s-size" % name, GLib.Variant("ai", [size[0], size[1]])) position = widget.get_position() Lp().settings.set_value("%s-position" % name, GLib.Variant("ai", [position[0], position[1]])) def __on_window_state_event(self, widget, event): """ Save maximised state """ Lp().settings.set_boolean( "window-maximized", "GDK_WINDOW_STATE_MAXIMIZED" in event.new_window_state.value_names) def __on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ if self.__was_maximized and\ self.__main_stack.get_visible_child_name() == "mini": Lp().settings.set_boolean("window-maximized", True) main_pos = self._paned_main_list.get_position() listview_pos = self._paned_list_view.get_position() listview_pos = listview_pos if listview_pos > 100 else 100 Lp().settings.set_value("paned-mainlist-width", GLib.Variant("i", main_pos)) Lp().settings.set_value("paned-listview-width", GLib.Variant("i", listview_pos)) def __on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp().player.position seek = position / 1000000 / 60 + seconds if seek < 0: seek = 0 if seek > Lp().player.current_track.duration: seek = Lp().player.current_track.duration - 2 Lp().player.seek(seek) if Lp().player.current_track.id is not None: self.__toolbar.update_position(seek * 60) def __on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp().player.play_pause() elif string == "play": Lp().player.play() elif string == "stop": Lp().player.stop() elif string == "next": Lp().player.next() elif string == "next_album": Lp().player.skip_album() elif string == "prev": Lp().player.prev() elif string == "locked": Lp().player.lock() elif string == "hide_pane": self._hide_pane() elif string == "filter": if self.view is not None: self.view.set_search_mode() elif string == "volume": self.__toolbar.show_hide_volume_control() elif string == "show_genres": state = not Lp().settings.get_value("show-genres") Lp().settings.set_value("show-genres", GLib.Variant("b", state)) Lp().window.show_genres(state) elif string == "loved": if Lp().player.current_track.id is not None and\ Lp().player.current_track.id >= 0: isloved = is_loved(Lp().player.current_track.id) set_loved(Lp().player.current_track.id, not isloved) if Lp().notify is not None: if isloved: heart = "♡" else: heart = "❤" Lp().notify.send( "%s - %s: %s" % (", ".join(Lp().player.current_track.artists), Lp().player.current_track.name, heart)) def __on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp().settings.get_value("auto-update") or Lp().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db) def __on_current_changed(self, player): """ Update toolbar @param player as Player """ if Lp().player.current_track.id is None: self.set_title("Lollypop") else: self.set_title(", ".join(player.current_track.artists) + " - " + player.current_track.title + " - Lollypop")
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self, app): """ Init window """ Container.__init__(self) self._app = app self._signal1 = None self._signal2 = None self._timeout = None self._was_maximized = False Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self.connect('notify::is-active', self._on_active) self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._main_stack = Gtk.Stack() self._main_stack.set_transition_duration(1000) self._main_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self._main_stack.show() self._setup_content() self.setup_window() self._setup_media_keys() self._enabled_shorcuts = False self.enable_global_shorcuts(True) self.connect('destroy', self._on_destroyed_window) self.connect('realize', self._on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self._toolbar.setup_menu(menu) def enable_global_shorcuts(self, enable): """ Setup global shortcuts @param enable as bool """ if self._enabled_shorcuts == enable: return self._enabled_shorcuts = enable if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) self._app.set_accels_for_action("app.player::next", ["n"]) self._app.set_accels_for_action("app.player::next_album", ["<Control>n"]) self._app.set_accels_for_action("app.player::prev", ["p"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::next", [None]) self._app.set_accels_for_action("app.player::next_album", [None]) self._app.set_accels_for_action("app.player::prev", [None]) def do_hide(self): """ Remove callbacks (we don't want to save an invalid value on hide """ if self._signal1 is not None: self.disconnect(self._signal1) self._signal1 = None if self._signal2 is not None: self.disconnect(self._signal2) self._signal2 = None Gtk.ApplicationWindow.do_hide(self) def setup_window(self): """ Setup window position and size, callbacks """ self._setup_pos_size('window') if Lp().settings.get_value('window-maximized'): self.maximize() if self._signal1 is None: self._signal1 = self.connect("window-state-event", self._on_window_state_event) if self._signal2 is None: self._signal2 = self.connect("configure-event", self._on_configure_event) def responsive_design(self): """ Handle responsive design """ size = self.get_size() self._toolbar.set_content_width(size[0]) view = self._stack.get_visible_child() if view and hasattr(view, 'show_context'): view.show_context(size[0] > WindowSize.MEDIUM) if Lp().player.current_track.id is not None: self._show_miniplayer(size[0] < WindowSize.MEDIUM) self._show_subtoolbar(size[0] < WindowSize.MONSTER and size[0] > WindowSize.MEDIUM) def set_mini(self): """ Set mini player on/off """ if Lp().player.current_track.id is None: return was_maximized = self.is_maximized() if self._main_stack.get_visible_child_name() == 'main': if self.is_maximized(): self.unmaximize() GLib.timeout_add(100, self._setup_pos_size, 'mini') else: self._setup_pos_size('mini') elif self._was_maximized: self.maximize() else: self._setup_pos_size('window') self._was_maximized = was_maximized ############ # Private # ############ def _show_subtoolbar(self, show): """ Show/hide subtoolbar @param show as bool """ is_visible = self._subtoolbar.is_visible() if show and not is_visible: mini = MiniPlayer() mini.show() self._subtoolbar.add(mini) self._subtoolbar.show() elif not show and is_visible: children = self._subtoolbar.get_children() if children: children[0].destroy() self._subtoolbar.hide() def _show_miniplayer(self, show): """ Show/hide miniplayer @param show as bool """ mini = self._main_stack.get_child_by_name('mini') if show: if mini is not None: if self._timeout is not None: GLib.source_remove(self._timeout) else: mini = MiniPlayer() self._main_stack.add_named(mini, 'mini') self._timeout = None mini.show() self._main_stack.set_visible_child_name('mini') self._toolbar.set_show_close_button(False) elif mini is not None and not show and self._timeout is None: self._main_stack.set_visible_child_name('main') self._toolbar.set_show_close_button( not Lp().settings.get_value('disable-csd')) self._timeout = GLib.timeout_add(1000, mini.destroy) def _setup_pos_size(self, name): """ Set window pos and size based on name @param name as str """ size_setting = Lp().settings.get_value('%s-size' % name) if len(size_setting) == 2 and\ isinstance(size_setting[0], int) and\ isinstance(size_setting[1], int): self.resize(size_setting[0], size_setting[1]) if name == 'window': self._setup_pos(name) else: # We need position to happen after resize as previous # may be refused by window manager => mini player as bottom GLib.idle_add(self._setup_pos, name) def _setup_pos(self, name): """ Set window position @param name as str """ position_setting = Lp().settings.get_value('%s-position' % name) 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]) def _setup_media_keys(self): """ Setup media player keys """ 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 def _grab_media_player_keys(self): """ Do key grabbing """ 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 def _handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp().player.play_pause() elif 'Stop' in response: Lp().player.stop() elif 'Next' in response: Lp().player.next() elif 'Previous' in response: Lp().player.prev() def _setup_content(self): """ Setup window content """ self.set_default_icon_name('lollypop') vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self._toolbar = Toolbar(self.get_application()) self._toolbar.show() self._subtoolbar = Gtk.Grid() if Lp().settings.get_value('disable-csd') or is_unity(): vgrid.add(self._toolbar) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) vgrid.add(self._main_stack) vgrid.add(self._subtoolbar) self.add(vgrid) self._main_stack.add_named(self.main_widget(), 'main') self._main_stack.set_visible_child_name('main') def _on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = None self.responsive_design() if not self.is_maximized(): self._timeout_configure = GLib.timeout_add( 1000, self._save_size_position, widget) def _save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self._timeout_configure = None size = widget.get_size() if size[0] > WindowSize.MEDIUM: name = 'window' else: name = 'mini' Lp().settings.set_value('%s-size' % name, GLib.Variant('ai', [size[0], size[1]])) position = widget.get_position() Lp().settings.set_value('%s-position' % name, GLib.Variant('ai', [position[0], position[1]])) def _on_window_state_event(self, widget, event): """ Save maximised state """ Lp().settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names) def _on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ if self._was_maximized and\ self._main_stack.get_visible_child_name() == 'mini': Lp().settings.set_boolean('window-maximized', True) Lp().settings.set_value('paned-mainlist-width', GLib.Variant('i', self._paned_main_list. get_position())) Lp().settings.set_value('paned-listview-width', GLib.Variant('i', self._paned_list_view. get_position())) def _on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp().player.get_position_in_track() seek = position/1000000/60+seconds if seek < 0: seek = 0 if seek > Lp().player.current_track.duration: seek = Lp().player.current_track.duration - 2 Lp().player.seek(seek) self._toolbar.update_position(seek*60) def _on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp().player.play_pause() elif string == "play": Lp().player.play() elif string == "stop": Lp().player.stop() elif string == "next": Lp().player.next() elif string == "next_album": # In party or shuffle, just update next track if Lp().player.is_party() or\ Lp().settings.get_enum('shuffle') == Shuffle.TRACKS: Lp().player.set_next() # We send this signal to update next popover Lp().player.emit('queue-changed') else: Lp().player.context.next = NextContext.START_NEW_ALBUM Lp().player.set_next() Lp().player.next() elif string == "prev": Lp().player.prev() def _on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp().settings.get_value('auto-update') or Lp().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db) def _on_active(self, window, active): """ Clean overlays if not active @param widget as Gtk.Window @param active as boolean """ if not window.is_active(): self.update_overlays()
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self, app): """ Init window """ Container.__init__(self) self._app = app self._signal1 = None self._signal2 = None Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self._nullwidget = Gtk.Label() # Use to get selected background color self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._setup_content() self._setup_window() self._setup_media_keys() self.enable_global_shorcuts(True) self.connect('destroy', self._on_destroyed_window) self.connect('realize', self._on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self._toolbar.setup_menu_btn(menu) def get_selected_color(self): """ Return selected color """ return self._nullwidget.get_style_context().\ get_background_color(Gtk.StateFlags.SELECTED) def enable_global_shorcuts(self, enable): """ Setup global shortcuts @param enable as bool """ if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) self._app.set_accels_for_action("app.player::next", ["n"]) self._app.set_accels_for_action("app.player::next_album", ["<Control>n"]) self._app.set_accels_for_action("app.player::prev", ["p"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::next", [None]) self._app.set_accels_for_action("app.player::next_album", [None]) self._app.set_accels_for_action("app.player::prev", [None]) def do_hide(self): """ Remove callbacks (we don't want to save an invalid value on hide """ if self._signal1 is not None: self.disconnect(self._signal1) if self._signal2 is not None: self.disconnect(self._signal2) Gtk.ApplicationWindow.do_hide(self) ############ # Private # ############ def _setup_media_keys(self): """ Setup media player keys """ 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 def _grab_media_player_keys(self): """ Do key grabbing """ 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 def _handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp.player.play_pause() elif 'Stop' in response: Lp.player.stop() elif 'Next' in response: Lp.player.next() elif 'Previous' in response: Lp.player.prev() def _setup_content(self): """ Setup window content """ self.set_icon_name('lollypop') self._toolbar = Toolbar(self.get_application()) self._toolbar.show() if Lp.settings.get_value('disable-csd'): vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.add(self._toolbar) vgrid.add(self.main_widget()) vgrid.show() self.add(vgrid) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) def _setup_window(self): """ Setup window position and size, callbacks """ size_setting = Lp.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 = Lp.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 Lp.settings.get_value('window-maximized'): self.maximize() self._signal1 = self.connect("window-state-event", self._on_window_state_event) self._signal2 = self.connect("configure-event", self._on_configure_event) def _on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ self._toolbar.set_progress_width(widget.get_size()[0]/4) if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = GLib.timeout_add(500, self._save_size_position, widget) def _save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self._timeout_configure = None size = widget.get_size() Lp.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]])) position = widget.get_position() Lp.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]])) def _on_window_state_event(self, widget, event): """ Save maximised state """ Lp.settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names) def _on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ Lp.settings.set_value('paned-mainlist-width', GLib.Variant('i', self._paned_main_list. get_position())) Lp.settings.set_value('paned-listview-width', GLib.Variant('i', self._paned_list_view. get_position())) def _on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp.player.get_position_in_track() seek = position/1000000/60+seconds if seek < 0: seek = 0 if seek > Lp.player.current_track.duration: seek = Lp.player.current_track.duration - 2 Lp.player.seek(seek) self._toolbar.update_position(seek*60) def _on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp.player.play_pause() elif string == "play": Lp.player.play() elif string == "stop": Lp.player.stop() elif string == "next": Lp.player.next() elif string == "next_album": # In party or shuffle, just update next track if Lp.player.is_party() or\ Lp.settings.get_enum('shuffle') == Shuffle.TRACKS: Lp.player.set_next() # We send this signal to update next popover Lp.player.emit("queue-changed") else: Lp.player.context.next = NextContext.START_NEW_ALBUM Lp.player.set_next() Lp.player.next() elif string == "prev": Lp.player.prev() def _on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp.settings.get_value('auto-update') or Lp.tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db)
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self): """ Init window """ Container.__init__(self) self.__signal1 = None self.__signal2 = None self.__timeout = None self.__was_maximized = False Gtk.ApplicationWindow.__init__(self, application=Lp(), title="Lollypop") self.connect('hide', self.__on_hide) Lp().player.connect('current-changed', self.__on_current_changed) self.__timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self.__on_seek_action) Lp().add_action(seek_action) player_action = Gio.SimpleAction.new('shortcut', GLib.VariantType.new('s')) player_action.connect('activate', self.__on_player_action) Lp().add_action(player_action) self.__setup_global_shortcuts() self.__main_stack = Gtk.Stack() self.__main_stack.set_transition_duration(1000) self.__main_stack.set_transition_type( Gtk.StackTransitionType.CROSSFADE) self.__main_stack.show() self.__setup_content() self.setup_window() self.__setup_media_keys() self.__enabled_shortcuts = False self.enable_global_shortcuts(True) self.connect('destroy', self.__on_destroyed_window) self.connect('realize', self.__on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self.__toolbar.setup_menu(menu) def enable_global_shortcuts(self, enable): """ Enable/Disable special global shortcuts @param enable as bool """ if self.__enabled_shortcuts == enable: return self.__enabled_shortcuts = enable if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: Lp().set_accels_for_action("app.seek(10)", ["Left"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Left"]) Lp().set_accels_for_action("app.seek(-10)", ["Right"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: Lp().set_accels_for_action("app.seek(10)", ["Right"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Right"]) Lp().set_accels_for_action("app.seek(-10)", ["Left"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Left"]) Lp().set_accels_for_action("app.shortcut::play_pause", ["space", "c"]) Lp().set_accels_for_action("app.shortcut::play", ["x"]) Lp().set_accels_for_action("app.shortcut::stop", ["v"]) Lp().set_accels_for_action("app.shortcut::next", ["n"]) Lp().set_accels_for_action("app.shortcut::prev", ["p"]) Lp().set_accels_for_action("app.shortcut::loved", ["l"]) else: Lp().set_accels_for_action("app.seek(10)", [None]) Lp().set_accels_for_action("app.seek(20)", [None]) Lp().set_accels_for_action("app.seek(-10)", [None]) Lp().set_accels_for_action("app.seek(-20)", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::next", [None]) Lp().set_accels_for_action("app.shortcut::next_album", [None]) Lp().set_accels_for_action("app.shortcut::prev", [None]) Lp().set_accels_for_action("app.shortcut::loved", [None]) def setup_window(self): """ Setup window position and size, callbacks """ self.__setup_pos_size('window') if Lp().settings.get_value('window-maximized'): self.maximize() if self.__signal1 is None: self.__signal1 = self.connect("window-state-event", self.__on_window_state_event) if self.__signal2 is None: self.__signal2 = self.connect("configure-event", self.__on_configure_event) def responsive_design(self): """ Handle responsive design """ size = self.get_size() self.__toolbar.set_content_width(size[0]) self.__show_miniplayer(size[0] < WindowSize.MEDIUM) self.__show_subtoolbar(size[0] < WindowSize.MONSTER and size[0] > WindowSize.MEDIUM) def set_mini(self): """ Set mini player on/off """ if Lp().player.current_track.id is None: return was_maximized = self.is_maximized() if self.__main_stack.get_visible_child_name() == 'main': if self.is_maximized(): self.unmaximize() GLib.timeout_add(100, self.__setup_pos_size, 'mini') else: self.__setup_pos_size('mini') elif self.__was_maximized: self.maximize() else: self.__setup_pos_size('window') self.__was_maximized = was_maximized @property def toolbar(self): """ toolbar as Toolbar """ return self.__toolbar def do_event(self, event): """ Update overlays as internal widget may not have received the signal @param widget as Gtk.Widget @param event as Gdk.event """ if event.type == Gdk.EventType.FOCUS_CHANGE and self.view is not None: self.view.disable_overlay() Lp().player.preview.set_state(Gst.State.NULL) Gtk.ApplicationWindow.do_event(self, event) ############ # Private # ############ def __setup_global_shortcuts(self): """ Setup global shortcuts """ Lp().set_accels_for_action("app.shortcut::locked", ["<Control>l"]) Lp().set_accels_for_action("app.shortcut::filter", ["<Control>i"]) Lp().set_accels_for_action("app.shortcut::volume", ["<Alt>v"]) Lp().set_accels_for_action("app.shortcut::next_album", ["<Control>n"]) Lp().set_accels_for_action("app.shortcut::show_genres", ["<Control>g"]) Lp().set_accels_for_action('app.shortcut::hide_pane', ["<Control>h"]) Lp().set_accels_for_action('app.update_db', ["<Control>u"]) Lp().set_accels_for_action('app.settings', ["<Control>s"]) Lp().set_accels_for_action('app.fullscreen', ["F11", "F7"]) Lp().set_accels_for_action("app.mini", ["<Control>m"]) Lp().set_accels_for_action('app.about', ["F3"]) Lp().set_accels_for_action('app.shortcuts', ["F2"]) Lp().set_accels_for_action('app.help', ["F1"]) Lp().set_accels_for_action('app.quit', ["<Control>q"]) def __show_subtoolbar(self, show): """ Show/hide subtoolbar @param show as bool """ is_visible = self.__subtoolbar.is_visible() if show and not is_visible: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() mini.show() self.__subtoolbar.add(mini) self.__subtoolbar.show() elif not show and is_visible: children = self.__subtoolbar.get_children() if children: children[0].destroy() self.__subtoolbar.hide() def __show_miniplayer(self, show): """ Show/hide miniplayer @param show as bool """ mini = self.__main_stack.get_child_by_name('mini') if show: if mini is not None: if self.__timeout is not None: GLib.source_remove(self.__timeout) else: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() self.__main_stack.add_named(mini, 'mini') self.__timeout = None mini.show() self.__main_stack.set_visible_child_name('mini') self.__toolbar.set_show_close_button(False) elif mini is not None and not show and self.__timeout is None: self.__main_stack.set_visible_child_name('main') self.__toolbar.set_show_close_button( not Lp().settings.get_value('disable-csd') and not is_unity()) self.__timeout = GLib.timeout_add(1000, mini.destroy) def __setup_pos_size(self, name): """ Set window pos and size based on name @param name as str """ size_setting = Lp().settings.get_value('%s-size' % name) if len(size_setting) == 2 and\ isinstance(size_setting[0], int) and\ isinstance(size_setting[1], int): self.resize(size_setting[0], size_setting[1]) if name == 'window': self.__setup_pos(name) else: # We need position to happen after resize as previous # may be refused by window manager => mini player as bottom GLib.idle_add(self.__setup_pos, name) def __setup_pos(self, name): """ Set window position @param name as str """ position_setting = Lp().settings.get_value('%s-position' % name) 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]) def __setup_media_keys(self): """ Setup media player keys """ 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 def __grab_media_player_keys(self): """ Do key grabbing """ 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 def __handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp().player.play_pause() elif 'Stop' in response: Lp().player.stop() elif 'Next' in response: Lp().player.next() elif 'Previous' in response: Lp().player.prev() def __setup_content(self): """ Setup window content """ self.set_default_icon_name('lollypop') vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self.__toolbar = Toolbar() self.__toolbar.show() self.__subtoolbar = Gtk.Grid() if Lp().settings.get_value('disable-csd') or is_unity(): vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not Lp().settings.get_value('disable-csd')) vgrid.add(self.__main_stack) vgrid.add(self.__subtoolbar) self.add(vgrid) self.__main_stack.add_named(self._paned_main_list, 'main') self.__main_stack.set_visible_child_name('main') def __on_hide(self, window): """ Remove callbacks (we don't want to save an invalid value on hide @param window as GtkApplicationWindow """ if self.__signal1 is not None: self.disconnect(self.__signal1) self.__signal1 = None if self.__signal2 is not None: self.disconnect(self.__signal2) self.__signal2 = None def __on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ if self.__timeout_configure: GLib.source_remove(self.__timeout_configure) self.__timeout_configure = None self.responsive_design() if not self.is_maximized(): self.__timeout_configure = GLib.timeout_add( 1000, self.__save_size_position, widget) def __save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self.__timeout_configure = None size = widget.get_size() if size[0] > WindowSize.MEDIUM: name = 'window' else: name = 'mini' Lp().settings.set_value('%s-size' % name, GLib.Variant('ai', [size[0], size[1]])) position = widget.get_position() Lp().settings.set_value('%s-position' % name, GLib.Variant('ai', [position[0], position[1]])) def __on_window_state_event(self, widget, event): """ Save maximised state """ Lp().settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names) def __on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ if self.__was_maximized and\ self.__main_stack.get_visible_child_name() == 'mini': Lp().settings.set_boolean('window-maximized', True) main_pos = self._paned_main_list.get_position() listview_pos = self._paned_list_view.get_position() listview_pos = listview_pos if listview_pos > 100 else 100 Lp().settings.set_value('paned-mainlist-width', GLib.Variant('i', main_pos)) Lp().settings.set_value('paned-listview-width', GLib.Variant('i', listview_pos)) def __on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp().player.position seek = position/1000000/60+seconds if seek < 0: seek = 0 if seek > Lp().player.current_track.duration: seek = Lp().player.current_track.duration - 2 Lp().player.seek(seek) if Lp().player.current_track.id is not None: self.__toolbar.update_position(seek*60) def __on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp().player.play_pause() elif string == "play": Lp().player.play() elif string == "stop": Lp().player.stop() elif string == "next": Lp().player.next() elif string == "next_album": Lp().player.skip_album() elif string == "prev": Lp().player.prev() elif string == "locked": Lp().player.lock() elif string == "hide_pane": self._hide_pane() elif string == "filter": if self.view is not None: self.view.set_search_mode() elif string == "volume": self.__toolbar.show_hide_volume_control() elif string == "show_genres": state = not Lp().settings.get_value('show-genres') Lp().settings.set_value('show-genres', GLib.Variant('b', state)) Lp().window.show_genres(state) elif string == "loved": if Lp().player.current_track.id is not None: isloved = is_loved(Lp().player.current_track.id) set_loved(Lp().player.current_track.id, not isloved) if Lp().notify is not None: if isloved: heart = "♡" else: heart = "❤" Lp().notify.send("%s - %s: %s" % ( ", ".join(Lp().player.current_track.artists), Lp().player.current_track.name, heart)) def __on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp().settings.get_value('auto-update') or Lp().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db) def __on_current_changed(self, player): """ Update toolbar @param player as Player """ if Lp().player.current_track.id is None: self.set_title("Lollypop") else: self.set_title(", ".join(player.current_track.artists) + " - " + player.current_track.title + " - Lollypop")
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self): """ Init window """ Container.__init__(self) self.__signal1 = None self.__signal2 = None self.__timeout = None self.__was_maximized = False Gtk.ApplicationWindow.__init__(self, application=Lp(), title="Lollypop", icon_name="org.gnome.Lollypop") self.connect("hide", self.__on_hide) Lp().player.connect("current-changed", self.__on_current_changed) self.__timeout_configure = None seek_action = Gio.SimpleAction.new("seek", GLib.VariantType.new("i")) seek_action.connect("activate", self.__on_seek_action) Lp().add_action(seek_action) player_action = Gio.SimpleAction.new("shortcut", GLib.VariantType.new("s")) player_action.connect("activate", self.__on_player_action) Lp().add_action(player_action) self.__setup_global_shortcuts() self.__main_stack = Gtk.Stack() self.__main_stack.set_transition_duration(1000) self.__main_stack.set_transition_type( Gtk.StackTransitionType.CROSSFADE) self.__main_stack.show() self.__setup_content() self.setup_window() self.__enabled_shortcuts = False self.enable_global_shortcuts(True) self.connect("destroy", self.__on_destroyed_window) self.connect("realize", self.__on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self.__toolbar.setup_menu(menu) def enable_global_shortcuts(self, enable): """ Enable/Disable special global shortcuts @param enable as bool """ if self.__enabled_shortcuts == enable: return self.__enabled_shortcuts = enable if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: Lp().set_accels_for_action("app.seek(10)", ["Left"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Left"]) Lp().set_accels_for_action("app.seek(-10)", ["Right"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: Lp().set_accels_for_action("app.seek(10)", ["Right"]) Lp().set_accels_for_action("app.seek(20)", ["<Control>Right"]) Lp().set_accels_for_action("app.seek(-10)", ["Left"]) Lp().set_accels_for_action("app.seek(-20)", ["<Control>Left"]) Lp().set_accels_for_action("app.shortcut::play_pause", ["space", "c"]) Lp().set_accels_for_action("app.shortcut::play", ["x"]) Lp().set_accels_for_action("app.shortcut::stop", ["v"]) Lp().set_accels_for_action("app.shortcut::next", ["n"]) Lp().set_accels_for_action("app.shortcut::prev", ["p"]) Lp().set_accels_for_action("app.shortcut::loved", ["l"]) else: Lp().set_accels_for_action("app.seek(10)", [None]) Lp().set_accels_for_action("app.seek(20)", [None]) Lp().set_accels_for_action("app.seek(-10)", [None]) Lp().set_accels_for_action("app.seek(-20)", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::play_pause", [None]) Lp().set_accels_for_action("app.shortcut::play", [None]) Lp().set_accels_for_action("app.shortcut::stop", [None]) Lp().set_accels_for_action("app.shortcut::next", [None]) Lp().set_accels_for_action("app.shortcut::next_album", [None]) Lp().set_accels_for_action("app.shortcut::prev", [None]) Lp().set_accels_for_action("app.shortcut::loved", [None]) def setup_window(self): """ Setup window position and size, callbacks """ self.__setup_pos_size("window") if Lp().settings.get_value("window-maximized"): self.maximize() if self.__signal1 is None: self.__signal1 = self.connect("window-state-event", self.__on_window_state_event) if self.__signal2 is None: self.__signal2 = self.connect("configure-event", self.__on_configure_event) def responsive_design(self): """ Handle responsive design """ size = self.get_size() self.__toolbar.set_content_width(size[0]) self.__show_miniplayer(size[0] < WindowSize.MEDIUM) self.__show_subtoolbar(size[0] < WindowSize.MONSTER and size[0] > WindowSize.MEDIUM) def set_mini(self): """ Set mini player on/off """ if Lp().player.current_track.id is None: return was_maximized = self.is_maximized() if self.__main_stack.get_visible_child_name() == "main": if self.is_maximized(): self.unmaximize() GLib.timeout_add(100, self.__setup_pos_size, "mini") else: self.__setup_pos_size("mini") elif self.__was_maximized: self.maximize() else: self.__setup_pos_size("window") self.__was_maximized = was_maximized @property def toolbar(self): """ toolbar as Toolbar """ return self.__toolbar def do_event(self, event): """ Update overlays as internal widget may not have received the signal @param widget as Gtk.Widget @param event as Gdk.event """ if event.type == Gdk.EventType.FOCUS_CHANGE and self.view is not None: self.view.disable_overlay() Lp().player.preview.set_state(Gst.State.NULL) Gtk.ApplicationWindow.do_event(self, event) ############ # Private # ############ def __setup_global_shortcuts(self): """ Setup global shortcuts """ Lp().set_accels_for_action("app.shortcut::locked", ["<Control>l"]) Lp().set_accels_for_action("app.shortcut::filter", ["<Control>i"]) Lp().set_accels_for_action("app.shortcut::volume", ["<Alt>v"]) Lp().set_accels_for_action("app.shortcut::next_album", ["<Control>n"]) Lp().set_accels_for_action("app.shortcut::show_genres", ["<Control>g"]) Lp().set_accels_for_action("app.shortcut::hide_pane", ["<Control>h"]) Lp().set_accels_for_action("app.update_db", ["<Control>u"]) Lp().set_accels_for_action("app.settings", ["<Control>s"]) Lp().set_accels_for_action("app.fullscreen", ["F11", "F7"]) Lp().set_accels_for_action("app.mini", ["<Control>m"]) Lp().set_accels_for_action("app.about", ["F3"]) Lp().set_accels_for_action("app.shortcuts", ["F2"]) Lp().set_accels_for_action("app.help", ["F1"]) Lp().set_accels_for_action("app.quit", ["<Control>q"]) def __show_subtoolbar(self, show): """ Show/hide subtoolbar @param show as bool """ is_visible = self.__subtoolbar.is_visible() if show and not is_visible: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() mini.show() self.__subtoolbar.add(mini) self.__subtoolbar.show() elif not show and is_visible: children = self.__subtoolbar.get_children() if children: children[0].destroy() self.__subtoolbar.hide() def __show_miniplayer(self, show): """ Show/hide miniplayer @param show as bool """ mini = self.__main_stack.get_child_by_name("mini") if show: if mini is not None: if self.__timeout is not None: GLib.source_remove(self.__timeout) else: from lollypop.miniplayer import MiniPlayer mini = MiniPlayer() self.__main_stack.add_named(mini, "mini") self.__timeout = None mini.show() self.__main_stack.set_visible_child_name("mini") self.__toolbar.set_show_close_button(False) elif mini is not None and not show and self.__timeout is None: self.__main_stack.set_visible_child_name("main") self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd") and not is_unity()) self.__timeout = GLib.timeout_add(1000, mini.destroy) def __setup_pos_size(self, name): """ Set window pos and size based on name @param name as str """ size_setting = Lp().settings.get_value("%s-size" % name) if len(size_setting) == 2 and\ isinstance(size_setting[0], int) and\ isinstance(size_setting[1], int): self.resize(size_setting[0], size_setting[1]) if name == "window": self.__setup_pos(name) else: # We need position to happen after resize as previous # may be refused by window manager => mini player as bottom GLib.idle_add(self.__setup_pos, name) def __setup_pos(self, name): """ Set window position @param name as str """ position_setting = Lp().settings.get_value("%s-position" % name) 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]) def __setup_content(self): """ Setup window content """ vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self.__toolbar = Toolbar() self.__toolbar.show() self.__subtoolbar = Gtk.Grid() if Lp().settings.get_value("disable-csd") or is_unity(): vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not Lp().settings.get_value("disable-csd")) vgrid.add(self.__main_stack) vgrid.add(self.__subtoolbar) self.add(vgrid) self.__main_stack.add_named(self._paned_main_list, "main") self.__main_stack.set_visible_child_name("main") self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_text_targets() self.connect("drag-data-received", self.__on_drag_data_received) self.connect("drag-motion", self.__on_drag_motion) self.connect("drag-leave", self.__on_drag_leave) def __on_drag_data_received(self, widget, context, x, y, data, info, time): """ Import values @param widget as Gtk.Widget @param context as Gdk.DragContext @param x as int @param y as int @param data as Gtk.SelectionData @param info as int @param time as int """ from lollypop.collectionimporter import CollectionImporter importer = CollectionImporter() uris = data.get_text().strip("\n").split("\r") task_helper = TaskHelper() task_helper.run(importer.add, uris, callback=(self.update_db,)) def __on_drag_motion(self, widget, context, x, y, time): """ Add style @param widget as Gtk.Widget @param context as Gdk.DragContext @param x as int @param y as int @param time as int """ import_widget = self.__main_stack.get_child_by_name("import") if import_widget is None: import_widget = Gtk.Label() import_widget.set_markup(_("<span size='xx-large'>" "<b>Import music</b></span>")) import_widget.show() self.__main_stack.add_named(import_widget, "import") self.__main_stack.set_visible_child_name("import") def __on_drag_leave(self, widget, context, time): """ Remove style @param widget as Gtk.Widget @param context as Gdk.DragContext @param time as int """ self.__main_stack.set_visible_child_name("main") def __on_hide(self, window): """ Remove callbacks we don"t want to save an invalid value on hide @param window as GtkApplicationWindow """ if self.__signal1 is not None: self.disconnect(self.__signal1) self.__signal1 = None if self.__signal2 is not None: self.disconnect(self.__signal2) self.__signal2 = None def __on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ if self.__timeout_configure: GLib.source_remove(self.__timeout_configure) self.__timeout_configure = None self.responsive_design() if not self.is_maximized(): self.__timeout_configure = GLib.timeout_add( 1000, self.__save_size_position, widget) def __save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self.__timeout_configure = None size = widget.get_size() if size[0] > WindowSize.MEDIUM: name = "window" else: name = "mini" Lp().settings.set_value("%s-size" % name, GLib.Variant("ai", [size[0], size[1]])) position = widget.get_position() Lp().settings.set_value("%s-position" % name, GLib.Variant("ai", [position[0], position[1]])) def __on_window_state_event(self, widget, event): """ Save maximised state """ Lp().settings.set_boolean("window-maximized", "GDK_WINDOW_STATE_MAXIMIZED" in event.new_window_state.value_names) def __on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ if self.__was_maximized and\ self.__main_stack.get_visible_child_name() == "mini": Lp().settings.set_boolean("window-maximized", True) main_pos = self._paned_main_list.get_position() listview_pos = self._paned_list_view.get_position() listview_pos = listview_pos if listview_pos > 100 else 100 Lp().settings.set_value("paned-mainlist-width", GLib.Variant("i", main_pos)) Lp().settings.set_value("paned-listview-width", GLib.Variant("i", listview_pos)) def __on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp().player.position seek = position / Gst.SECOND + seconds if seek < 0: seek = 0 if seek > Lp().player.current_track.duration: seek = Lp().player.current_track.duration - 2 Lp().player.seek(seek) if Lp().player.current_track.id is not None: self.__toolbar.update_position(seek) def __on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp().player.play_pause() elif string == "play": Lp().player.play() elif string == "stop": Lp().player.stop() elif string == "next": Lp().player.next() elif string == "next_album": Lp().player.skip_album() elif string == "prev": Lp().player.prev() elif string == "locked": Lp().player.lock() elif string == "hide_pane": self._hide_pane() elif string == "filter": if self.view is not None: self.view.set_search_mode() elif string == "volume": self.__toolbar.show_hide_volume_control() elif string == "show_genres": state = not Lp().settings.get_value("show-genres") Lp().settings.set_value("show-genres", GLib.Variant("b", state)) Lp().window.show_genres(state) elif string == "loved": if Lp().player.current_track.id is not None and\ Lp().player.current_track.id >= 0: isloved = is_loved(Lp().player.current_track.id) set_loved(Lp().player.current_track.id, not isloved) if Lp().notify is not None: if isloved: heart = "♡" else: heart = "❤" Lp().notify.send("%s - %s: %s" % ( ", ".join(Lp().player.current_track.artists), Lp().player.current_track.name, heart)) def __on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp().settings.get_value("auto-update") or Lp().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db) def __on_current_changed(self, player): """ Update toolbar @param player as Player """ if Lp().player.current_track.id is None: self.set_title("Lollypop") else: self.set_title(", ".join(player.current_track.artists) + " - " + player.current_track.title + " - Lollypop")
class Window(Gtk.ApplicationWindow, SignalsHelper): """ Main window """ @signals_map def __init__(self): """ Init window """ Gtk.ApplicationWindow.__init__(self, application=App(), title="Lollypop", icon_name="org.gnome.Lollypop") self.__miniplayer = None self.__configure_timeout_id = None self.set_auto_startup_notification(False) self.connect("realize", self.__on_realize) # Does not work with a Gtk.Gesture in GTK3 self.connect("button-release-event", self.__on_button_release_event) self.connect("window-state-event", self.__on_window_state_event) self.connect("configure-event", self.__on_configure_event) self.connect("destroy", self.__on_destroy) return [(App().player, "current-changed", "_on_current_changed")] def setup(self): """ Setup window content """ self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__container = Container() self.__container.setup() self.__container.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.__container.widget.connect("notify::folded", self.__on_container_folded) def show_miniplayer(self, show, reveal=False): """ Show/hide subtoolbar @param show as bool @param reveal as bool """ def show_buttons(show): if show: self.toolbar.end.show() self.toolbar.playback.show() else: self.toolbar.end.hide() self.toolbar.playback.hide() def on_revealed(miniplayer, revealed): miniplayer.set_vexpand(revealed) show_buttons(not revealed) if revealed: self.__container.hide() emit_signal(self.__container, "can-go-back-changed", False) else: self.__container.show() emit_signal(self.__container, "can-go-back-changed", self.__container.can_go_back) if show and self.__miniplayer is None: from lollypop.miniplayer import MiniPlayer self.__miniplayer = MiniPlayer() if App().player.current_track.id is not None: self.__miniplayer.show() self.__miniplayer.connect("revealed", on_revealed) self.__vgrid.add(self.__miniplayer) self.__miniplayer.set_vexpand(False) elif not show and self.__miniplayer is not None: if App().lookup_action("miniplayer").get_state(): App().lookup_action("miniplayer").change_state( GLib.Variant("b", False)) else: self.__miniplayer.destroy() self.__miniplayer = None self.__container.show() show_buttons(True) if self.__miniplayer is not None: if reveal: self.__miniplayer.reveal(True) else: self.__miniplayer.update_artwork() @property def folded(self): """ True if window is adaptive, ie widget folded """ return App().window.container.widget.get_folded() @property def miniplayer(self): """ @return MiniPlayer """ return self.__miniplayer @property def toolbar(self): """ @return Toolbar """ return self.__toolbar @property def container(self): """ @return Container """ return self.__container ############## # PROTECTED # ############## def _on_current_changed(self, player): """ Update toolbar @param player as Player """ if App().player.current_track.id is None: self.set_title("Lollypop") else: artists = ", ".join(player.current_track.artists) self.set_title("%s - %s" % (artists, player.current_track.name)) if self.__miniplayer is not None: self.__miniplayer.show() def _on_configure_event_timeout(self, width, height, x, y): """ Setup content based on current size @param width as int @param height as int @param x as int @param y as int """ self.__configure_timeout_id = None if App().lookup_action("miniplayer").get_state(): return if not self.is_maximized(): # Keep a minimal height if height < AdaptiveSize.SMALL: height = AdaptiveSize.SMALL App().settings.set_value("window-size", GLib.Variant("ai", [width, height])) App().settings.set_value("window-position", GLib.Variant("ai", [x, y])) ############ # PRIVATE # ############ def __setup_size_and_position(self): """ Setup window position and size, callbacks """ try: size = App().settings.get_value("window-size") pos = App().settings.get_value("window-position") self.resize(size[0], size[1]) self.move(pos[0], pos[1]) if App().settings.get_value("window-maximized"): self.maximize() except Exception as e: Logger.error("Window::__setup_size_and_position(): %s", e) def __on_realize(self, window): """ Init window content @param window as Gtk.Window """ self.__setup_size_and_position() if App().settings.get_value("auto-update") or App().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(1000, App().scanner.update, ScanType.FULL) def __on_button_release_event(self, window, event): """ Handle special mouse buttons @param window as Gtk.Window @param event as Gdk.EventButton """ if event.button == 8: App().window.container.go_back() return True def __on_window_state_event(self, widget, event): """ Save maximised state """ if not App().lookup_action("miniplayer").get_state(): App().settings.set_boolean( "window-maximized", "GDK_WINDOW_STATE_MAXIMIZED" in event.new_window_state.value_names) def __on_container_folded(self, leaflet, folded): """ show/hide miniplayer @param leaflet as Handy.Leaflet @param folded as Gparam """ self.show_miniplayer(App().window.folded) def __on_destroy(self, widget): """ Remove ref cycle, just to prevent output on DEBUG_LEAK @param widget as Gtk.Widget """ self.__toolbar = None def __on_configure_event(self, window, event): """ Delay event @param window as Gtk.Window @param event as Gdk.EventConfigure """ if self.__configure_timeout_id: GLib.source_remove(self.__configure_timeout_id) (width, height) = window.get_size() (x, y) = window.get_position() self.__configure_timeout_id = GLib.idle_add( self._on_configure_event_timeout, width, height, x, y, priority=GLib.PRIORITY_LOW)
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self, app): """ Init window """ Container.__init__(self) self._app = app self._signal1 = None self._signal2 = None Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self._nullwidget = Gtk.Label() # Use to get selected background color self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._setup_content() self._setup_window() self._setup_media_keys() self.enable_global_shorcuts(True) self.connect('destroy', self._on_destroyed_window) self.connect('realize', self._on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self._toolbar.setup_menu_btn(menu) def get_selected_color(self): """ Return selected color """ return self._nullwidget.get_style_context().\ get_background_color(Gtk.StateFlags.SELECTED) def enable_global_shorcuts(self, enable): """ Setup global shortcuts @param enable as bool """ if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) self._app.set_accels_for_action("app.player::next", ["n"]) self._app.set_accels_for_action("app.player::next_album", ["<Control>n"]) self._app.set_accels_for_action("app.player::prev", ["p"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::next", [None]) self._app.set_accels_for_action("app.player::next_album", [None]) self._app.set_accels_for_action("app.player::prev", [None]) def do_hide(self): """ Remove callbacks (we don't want to save an invalid value on hide """ if self._signal1 is not None: self.disconnect(self._signal1) if self._signal2 is not None: self.disconnect(self._signal2) Gtk.ApplicationWindow.do_hide(self) ############ # Private # ############ def _setup_media_keys(self): """ Setup media player keys """ 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 def _grab_media_player_keys(self): """ Do key grabbing """ 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 def _handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp.player.play_pause() elif 'Stop' in response: Lp.player.stop() elif 'Next' in response: Lp.player.next() elif 'Previous' in response: Lp.player.prev() def _setup_content(self): """ Setup window content """ self.set_icon_name('lollypop') self._toolbar = Toolbar(self.get_application()) self._toolbar.show() if Lp.settings.get_value('disable-csd'): vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.add(self._toolbar) vgrid.add(self.main_widget()) vgrid.show() self.add(vgrid) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) def _setup_window(self): """ Setup window position and size, callbacks """ size_setting = Lp.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 = Lp.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 Lp.settings.get_value('window-maximized'): self.maximize() self._signal1 = self.connect("window-state-event", self._on_window_state_event) self._signal2 = self.connect("configure-event", self._on_configure_event) def _on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ self._toolbar.set_progress_width(widget.get_size()[0] / 4) if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = GLib.timeout_add(500, self._save_size_position, widget) def _save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self._timeout_configure = None size = widget.get_size() Lp.settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]])) position = widget.get_position() Lp.settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]])) def _on_window_state_event(self, widget, event): """ Save maximised state """ Lp.settings.set_boolean( 'window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names) def _on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ Lp.settings.set_value( 'paned-mainlist-width', GLib.Variant('i', self._paned_main_list.get_position())) Lp.settings.set_value( 'paned-listview-width', GLib.Variant('i', self._paned_list_view.get_position())) def _on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp.player.get_position_in_track() seek = position / 1000000 / 60 + seconds if seek < 0: seek = 0 if seek > Lp.player.current_track.duration: seek = Lp.player.current_track.duration - 2 Lp.player.seek(seek) self._toolbar.update_position(seek * 60) def _on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp.player.play_pause() elif string == "play": Lp.player.play() elif string == "stop": Lp.player.stop() elif string == "next": Lp.player.next() elif string == "next_album": # In party or shuffle, just update next track if Lp.player.is_party() or\ Lp.settings.get_enum('shuffle') == Shuffle.TRACKS: Lp.player.set_next() # We send this signal to update next popover Lp.player.emit("queue-changed") else: Lp.player.context.next = NextContext.START_NEW_ALBUM Lp.player.set_next() Lp.player.next() elif string == "prev": Lp.player.prev() def _on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp.settings.get_value('auto-update') or Lp.tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db)
class Window(Gtk.ApplicationWindow, Container): """ Main window """ def __init__(self, app): """ Init window """ Container.__init__(self) self._app = app self._signal1 = None self._signal2 = None self._timeout = None self._was_maximized = False Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self.connect('notify::is-active', self._on_active) self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._main_stack = Gtk.Stack() self._main_stack.set_transition_duration(1000) self._main_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) self._main_stack.show() self._setup_content() self.setup_window() self._setup_media_keys() self._enabled_shorcuts = False self.enable_global_shorcuts(True) self.connect('destroy', self._on_destroyed_window) self.connect('realize', self._on_realize) def setup_menu(self, menu): """ Add an application menu to window @parma: menu as Gio.Menu """ self._toolbar.setup_menu(menu) def enable_global_shorcuts(self, enable): """ Setup global shortcuts @param enable as bool """ if self._enabled_shorcuts == enable: return self._enabled_shorcuts = enable if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) self._app.set_accels_for_action("app.player::next", ["n"]) self._app.set_accels_for_action("app.player::next_album", ["<Control>n"]) self._app.set_accels_for_action("app.player::prev", ["p"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) self._app.set_accels_for_action("app.player::next", [None]) self._app.set_accels_for_action("app.player::next_album", [None]) self._app.set_accels_for_action("app.player::prev", [None]) def do_hide(self): """ Remove callbacks (we don't want to save an invalid value on hide """ if self._signal1 is not None: self.disconnect(self._signal1) self._signal1 = None if self._signal2 is not None: self.disconnect(self._signal2) self._signal2 = None Gtk.ApplicationWindow.do_hide(self) def setup_window(self): """ Setup window position and size, callbacks """ self._setup_pos_size('window') if Lp().settings.get_value('window-maximized'): self.maximize() if self._signal1 is None: self._signal1 = self.connect("window-state-event", self._on_window_state_event) if self._signal2 is None: self._signal2 = self.connect("configure-event", self._on_configure_event) def responsive_design(self): """ Handle responsive design """ size = self.get_size() self._toolbar.set_content_width(size[0]) view = self._stack.get_visible_child() if view and hasattr(view, 'show_context'): view.show_context(size[0] > WindowSize.MEDIUM) if Lp().player.current_track.id is not None: self._show_miniplayer(size[0] < WindowSize.MEDIUM) self._show_subtoolbar(size[0] < WindowSize.MONSTER and size[0] > WindowSize.MEDIUM) def set_mini(self): """ Set mini player on/off """ if Lp().player.current_track.id is None: return was_maximized = self.is_maximized() if self._main_stack.get_visible_child_name() == 'main': if self.is_maximized(): self.unmaximize() GLib.timeout_add(100, self._setup_pos_size, 'mini') else: self._setup_pos_size('mini') elif self._was_maximized: self.maximize() else: self._setup_pos_size('window') self._was_maximized = was_maximized ############ # Private # ############ def _show_subtoolbar(self, show): """ Show/hide subtoolbar @param show as bool """ is_visible = self._subtoolbar.is_visible() if show and not is_visible: mini = MiniPlayer() mini.show() self._subtoolbar.add(mini) self._subtoolbar.show() elif not show and is_visible: children = self._subtoolbar.get_children() if children: children[0].destroy() self._subtoolbar.hide() def _show_miniplayer(self, show): """ Show/hide miniplayer @param show as bool """ mini = self._main_stack.get_child_by_name('mini') if show: if mini is not None: if self._timeout is not None: GLib.source_remove(self._timeout) else: mini = MiniPlayer() self._main_stack.add_named(mini, 'mini') self._timeout = None mini.show() self._main_stack.set_visible_child_name('mini') self._toolbar.set_show_close_button(False) elif mini is not None and not show and self._timeout is None: self._main_stack.set_visible_child_name('main') self._toolbar.set_show_close_button( not Lp().settings.get_value('disable-csd')) self._timeout = GLib.timeout_add(1000, mini.destroy) def _setup_pos_size(self, name): """ Set window pos and size based on name @param name as str """ size_setting = Lp().settings.get_value('%s-size' % name) if len(size_setting) == 2 and\ isinstance(size_setting[0], int) and\ isinstance(size_setting[1], int): self.resize(size_setting[0], size_setting[1]) if name == 'window': self._setup_pos(name) else: # We need position to happen after resize as previous # may be refused by window manager => mini player as bottom GLib.idle_add(self._setup_pos, name) def _setup_pos(self, name): """ Set window position @param name as str """ position_setting = Lp().settings.get_value('%s-position' % name) 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]) def _setup_media_keys(self): """ Setup media player keys """ 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 def _grab_media_player_keys(self): """ Do key grabbing """ 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 def _handle_media_keys(self, proxy, sender, signal, parameters): """ Do player actions in response to media key pressed """ 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: Lp().player.play_pause() elif 'Stop' in response: Lp().player.stop() elif 'Next' in response: Lp().player.next() elif 'Previous' in response: Lp().player.prev() def _setup_content(self): """ Setup window content """ self.set_default_icon_name('lollypop') vgrid = Gtk.Grid() vgrid.set_orientation(Gtk.Orientation.VERTICAL) vgrid.show() self._toolbar = Toolbar(self.get_application()) self._toolbar.show() self._subtoolbar = Gtk.Grid() if Lp().settings.get_value('disable-csd') or is_unity(): vgrid.add(self._toolbar) else: self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) vgrid.add(self._main_stack) vgrid.add(self._subtoolbar) self.add(vgrid) self._main_stack.add_named(self.main_widget(), 'main') self._main_stack.set_visible_child_name('main') def _on_configure_event(self, widget, event): """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = None self.responsive_design() if not self.is_maximized(): self._timeout_configure = GLib.timeout_add( 1000, self._save_size_position, widget) def _save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self._timeout_configure = None size = widget.get_size() if size[0] > WindowSize.MEDIUM: name = 'window' else: name = 'mini' Lp().settings.set_value('%s-size' % name, GLib.Variant('ai', [size[0], size[1]])) position = widget.get_position() Lp().settings.set_value('%s-position' % name, GLib.Variant('ai', [position[0], position[1]])) def _on_window_state_event(self, widget, event): """ Save maximised state """ Lp().settings.set_boolean( 'window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names) def _on_destroyed_window(self, widget): """ Save paned widget width @param widget as unused, data as unused """ if self._was_maximized and\ self._main_stack.get_visible_child_name() == 'mini': Lp().settings.set_boolean('window-maximized', True) Lp().settings.set_value( 'paned-mainlist-width', GLib.Variant('i', self._paned_main_list.get_position())) Lp().settings.set_value( 'paned-listview-width', GLib.Variant('i', self._paned_list_view.get_position())) def _on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = Lp().player.get_position_in_track() seek = position / 1000000 / 60 + seconds if seek < 0: seek = 0 if seek > Lp().player.current_track.duration: seek = Lp().player.current_track.duration - 2 Lp().player.seek(seek) self._toolbar.update_position(seek * 60) def _on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": Lp().player.play_pause() elif string == "play": Lp().player.play() elif string == "stop": Lp().player.stop() elif string == "next": Lp().player.next() elif string == "next_album": # In party or shuffle, just update next track if Lp().player.is_party() or\ Lp().settings.get_enum('shuffle') == Shuffle.TRACKS: Lp().player.set_next() # We send this signal to update next popover Lp().player.emit('queue-changed') else: Lp().player.context.next = NextContext.START_NEW_ALBUM Lp().player.set_next() Lp().player.next() elif string == "prev": Lp().player.prev() def _on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ if Lp().settings.get_value('auto-update') or Lp().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, self.update_db) def _on_active(self, window, active): """ Clean overlays if not active @param widget as Gtk.Window @param active as boolean """ if not window.is_active(): self.update_overlays()
class Window(Gtk.ApplicationWindow, Container): """ Init window objects """ def __init__(self, app): Container.__init__(self) self._app = app Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self._nullwidget = Gtk.Label() # Use to get selected background color self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._setup_window() self._setup_media_keys() self.enable_global_shorcuts(True) self.connect("destroy", self._on_destroyed_window) """ Add an application menu to window @parma: menu as Gio.Menu """ def setup_menu(self, menu): self._toolbar.setup_menu_btn(menu) """ Return selected color """ def get_selected_color(self): return self._nullwidget.get_style_context().get_background_color( Gtk.StateFlags.SELECTED) """ Setup global shortcuts @param enable as bool """ def enable_global_shorcuts(self, enable): if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) ############ # 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) self._toolbar = Toolbar(self.get_application()) self._toolbar.show() # Only set headerbar if according DE detected or forced manually if use_csd(): self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) else: hgrid = Gtk.Grid() hgrid.set_orientation(Gtk.Orientation.VERTICAL) hgrid.add(self._toolbar) hgrid.add(self.main_widget()) hgrid.show() self.add(hgrid) """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ def _on_configure_event(self, widget, event): self._toolbar.set_progress_width(widget.get_size()[0] / 4) if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = 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_configure = 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())) """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ def _on_seek_action(self, action, param): seconds = param.get_int32() position = Objects.player.get_position_in_track() seek = position / 1000000 / 60 + seconds if seek < 0: seek = 0 if seek > Objects.player.current.duration: seek = Objects.player.current.duration - 2 Objects.player.seek(seek) self._toolbar.update_position(seek * 60) """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ def _on_player_action(self, action, param): string = param.get_string() if string == "play_pause": Objects.player.play_pause() elif string == "play": Objects.player.play() elif string == "stop": Objects.player.stop()
class Window(Gtk.ApplicationWindow, AdaptiveWindow): """ Main window """ def __init__(self): """ Init window """ Gtk.ApplicationWindow.__init__(self, application=App(), title="Lollypop", icon_name="org.gnome.Lollypop") AdaptiveWindow.__init__(self) self.__signal1 = None self.__signal2 = None self.__timeout = None self.__miniplayer = None self.__mediakeys = None self.__sidebar_shown = False self.__media_keys_busnames = [] self.__headerbar_buttons_width = get_headerbar_buttons_width() self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) App().player.connect("current-changed", self.__on_current_changed) self.__timeout_configure = None self.__setup_content() # FIXME Remove this, handled by MPRIS in GNOME 3.26 self.__setup_media_keys() self.set_auto_startup_notification(False) self.connect("realize", self.__on_realize) self.connect("adaptive-changed", self.__on_adaptive_changed) self.connect("button-release-event", self.__on_button_release_event) self.connect("focus-out-event", self.__on_focus_out_event) @property def miniplayer(self): """ True if miniplayer is on @return bool """ return self.__miniplayer is not None @property def toolbar(self): """ toolbar as Toolbar """ return self.__toolbar @property def container(self): """ Get container @return Container """ return self.__container ############ # PRIVATE # ############ def __setup(self): """ Setup window position and size, callbacks """ size = App().settings.get_value("window-size") pos = App().settings.get_value("window-position") self.__setup_size(size) self.__setup_pos(pos) if App().settings.get_value("window-maximized"): # Lets resize happen GLib.idle_add(self.maximize) self.do_adaptive_mode(self._ADAPTIVE_STACK) else: self.do_adaptive_mode(size[0]) def __show_miniplayer(self, show): """ Show/hide subtoolbar @param show as bool """ def on_revealed(miniplayer, revealed): miniplayer.set_vexpand(revealed) if revealed: self.__container.hide() self.emit("can-go-back-changed", False) self.toolbar.end.home_button.set_sensitive(False) else: self.__container.show() self.emit("can-go-back-changed", self.can_go_back) self.toolbar.end.home_button.set_sensitive(True) if show and self.__miniplayer is None: from lollypop.miniplayer import MiniPlayer self.__miniplayer = MiniPlayer() self.__miniplayer.connect("revealed", on_revealed) self.__miniplayer.set_vexpand(False) self.__vgrid.add(self.__miniplayer) self.__toolbar.set_mini(True) elif not show and self.__miniplayer is not None: self.__toolbar.set_mini(False) self.__miniplayer.destroy() self.__miniplayer = None def __setup_size(self, size): """ Set window size @param size as (int, int) """ if len(size) == 2 and\ isinstance(size[0], int) and\ isinstance(size[1], int): self.resize(size[0], size[1]) def __setup_pos(self, pos): """ Set window position @param pos as (int, int) """ if len(pos) == 2 and\ isinstance(pos[0], int) and\ isinstance(pos[1], int): self.move(pos[0], pos[1]) # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __setup_media_keys(self): """ Setup media player keys """ self.__media_keys_busnames = [ "org.gnome.SettingDaemon.MediaKeys", "org.gnome.SettingsDaemon", ] self.__get_media_keys_proxy() # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __get_media_keys_proxy(self): if self.__media_keys_busnames: bus_name = self.__media_keys_busnames.pop(0) try: bus = App().get_dbus_connection() Gio.DBusProxy.new( bus, Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, None, bus_name, "/org/gnome/SettingsDaemon/MediaKeys", "org.gnome.SettingsDaemon.MediaKeys", None, self.__on_get_proxy, ) except Exception as e: Logger.error("Window::__setup_media_keys(): %s" % e) # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __on_get_proxy(self, source, result): try: self.__mediakeys = source.new_finish(result) except Exception as e: self.__mediakeys = None Logger.error("Window::__on_get_proxy(): %s" % e) else: if self.__mediakeys.get_name_owner(): self.__grab_media_keys() self.__mediakeys.connect('g-signal', self.__mediakey_signal) else: self.__mediakeys = None self.__get_media_keys_proxy() # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __grab_media_keys(self): if not self.__mediakeys: return self.__mediakeys.call( "GrabMediaPlayerKeys", GLib.Variant("(su)", ("org.gnome.Lollypop", 0)), Gio.DBusCallFlags.NONE, -1, None, None, ) def __mediakey_signal(self, proxy, sender, signal, param, userdata=None): if signal != "MediaPlayerKeyPressed": return app, action = param.unpack() if app == "org.gnome.Lollypop": if action == "Play": App().player.play_pause() elif action == "Next": App().player.next() elif action == "Stop": App().player.stop() elif action == "Previous": App().player.prev() def __setup_content(self): """ Setup window content """ self.__container = Container() self.set_stack(self.container.stack) self.__container.show() self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_uri_targets() self.connect("drag-data-received", self.__on_drag_data_received) def __handle_miniplayer(self, width, height): """ Handle mini player show/hide @param width as int @param height as int """ if width - self.__headerbar_buttons_width < Sizing.MONSTER: self.__show_miniplayer(True) else: self.__show_miniplayer(False) self.__container.show() def __show_sidebar(self, show): """ Show sidebar if needed @param show as bool """ if self.__sidebar_shown: return self.__sidebar_shown = True self.__container.show_sidebar(show) def __save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self.__timeout_configure = None (width, height) = widget.get_size() # Keep a minimal height if height < Sizing.MEDIUM: height = Sizing.MEDIUM App().settings.set_value("window-size", GLib.Variant("ai", [width, height])) (x, y) = widget.get_position() App().settings.set_value("window-position", GLib.Variant("ai", [x, y])) def __on_window_state_event(self, widget, event): """ Save maximised state """ App().settings.set_boolean( "window-maximized", "GDK_WINDOW_STATE_MAXIMIZED" in event.new_window_state.value_names) if event.changed_mask & Gdk.WindowState.FOCUSED and \ event.new_window_state & Gdk.WindowState.FOCUSED: # FIXME Remove this, handled by MPRIS in GNOME 3.26 self.__grab_media_keys() def __on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ self.__setup() if App().settings.get_value("auto-update") or App().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(1000, App().scanner.update, ScanType.FULL) # Here we ignore initial configure events self.__toolbar.set_content_width(self.get_size()[0]) def __on_current_changed(self, player): """ Update toolbar @param player as Player """ if App().player.current_track.id is None: self.set_title("Lollypop") else: name = player.current_track.name name = re.sub("\(.*\)", "", name).strip() artist = player.current_track.artists[0] artist = re.sub("\(.*\)", "", artist).strip() self.set_title("%s - %s" % (name, artist)) def __on_focus_out_event(self, window, event): """ Disable overlay on children @param window as Gtk.Window @param event as Gdk.EventFocus """ if self.__container.view is not None: self.__container.view.disable_overlay() def __on_button_release_event(self, window, event): """ Handle special mouse buttons @param window as Gtk.Window @param event as Gdk.EventButton """ if event.button == 8: App().window.go_back() def __on_drag_data_received(self, widget, context, x, y, data, info, time): """ Import values @param widget as Gtk.Widget @param context as Gdk.DragContext @param x as int @param y as int @param data as Gtk.SelectionData @param info as int @param time as int """ try: from lollypop.collectionimporter import CollectionImporter from urllib.parse import urlparse importer = CollectionImporter() uris = [] for uri in data.get_text().strip("\n").split("\r"): parsed = urlparse(uri) if parsed.scheme in ["file", "sftp", "smb", "webdav"]: uris.append(uri) if uris: App().task_helper.run(importer.add, uris, callback=(App().scanner.update, )) except: pass def __on_map(self, window): """ Connect signals @param window as Window """ if self.__signal1 is None: self.__signal1 = self.connect("window-state-event", self.__on_window_state_event) if self.__signal2 is None: self.__signal2 = self.connect("configure-event", self.__on_configure_event) def __on_unmap(self, window): """ Disconnect signals @param window as Window """ if self.__signal1 is not None: self.disconnect(self.__signal1) self.__signal1 = None if self.__signal2 is not None: self.disconnect(self.__signal2) self.__signal2 = None def __on_configure_event(self, window, event): """ Handle configure event @param window as Gtk.Window @param event as Gdk.Event """ (width, height) = window.get_size() self.__handle_miniplayer(width, height) self.__toolbar.set_content_width(width) if self.__timeout_configure: GLib.source_remove(self.__timeout_configure) self.__timeout_configure = None if not self.is_maximized(): self.__timeout_configure = GLib.timeout_add( 1000, self.__save_size_position, window) def __on_adaptive_changed(self, window, adaptive_stack): """ Handle adaptive mode @param window as AdaptiveWindow @param adaptive_stack as bool """ show_sidebar = App().settings.get_value("show-sidebar") self.__show_sidebar(show_sidebar) if show_sidebar: widget = self.__container.list_one else: widget = self.__container.rounded_artists_view if adaptive_stack: self.__toolbar.end.set_mini(True) widget.add_value((Type.SEARCH, _("Search"), _("Search"))) widget.add_value( (Type.CURRENT, _("Current playlist"), _("Current playlist"))) self.emit("show-can-go-back", True) else: self.__toolbar.end.set_mini(False) widget.remove_value(Type.CURRENT) widget.remove_value(Type.SEARCH) if App().settings.get_value("show-sidebar"): self.emit("show-can-go-back", False) self.emit("can-go-back-changed", self.can_go_back)
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 Window(Gtk.ApplicationWindow, AdaptiveWindow): """ Main window """ def __init__(self): """ Init window """ Gtk.ApplicationWindow.__init__(self, application=App(), title="Lollypop", icon_name="org.gnome.Lollypop") AdaptiveWindow.__init__(self) self.__signal1 = None self.__signal2 = None self.__timeout = None self.__miniplayer = None self.__mediakeys = None self.__media_keys_busnames = [] self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) App().player.connect("current-changed", self.__on_current_changed) self.__timeout_configure = None seek_action = Gio.SimpleAction.new("seek", GLib.VariantType.new("i")) seek_action.connect("activate", self.__on_seek_action) App().add_action(seek_action) player_action = Gio.SimpleAction.new("shortcut", GLib.VariantType.new("s")) player_action.connect("activate", self.__on_player_action) App().add_action(player_action) self.__setup_global_shortcuts() self.__setup_content() # FIXME Remove this, handled by MPRIS in GNOME 3.26 self.__setup_media_keys() self.set_auto_startup_notification(False) self.connect("realize", self.__on_realize) self.connect("adaptive-changed", self.__on_adaptive_changed) def setup(self): """ Setup window position and size, callbacks """ size = App().settings.get_value("window-size") pos = App().settings.get_value("window-position") self.__setup_size(size) self.__setup_pos(pos) if App().settings.get_value("window-maximized"): # Lets resize happen GLib.idle_add(self.maximize) self.do_adaptive_mode(self._ADAPTIVE_STACK) else: self.do_adaptive_mode(size[0]) def do_event(self, event): """ Update overlays as internal widget may not have received the signal @param widget as Gtk.Widget @param event as Gdk.event """ if event.type == Gdk.EventType.FOCUS_CHANGE and\ self.__container.view is not None: self.__container.view.disable_overlay() App().player.preview.set_state(Gst.State.NULL) Gtk.ApplicationWindow.do_event(self, event) @property def miniplayer(self): """ True if miniplayer is on @return bool """ return self.__miniplayer is not None @property def toolbar(self): """ toolbar as Toolbar """ return self.__toolbar @property def container(self): """ Get container @return Container """ return self.__container ############ # PRIVATE # ############ def __setup_global_shortcuts(self): """ Setup global shortcuts """ App().set_accels_for_action("app.shortcut::locked", ["<Control>l"]) App().set_accels_for_action("app.shortcut::filter", ["<Control>i"]) App().set_accels_for_action("app.shortcut::volume", ["<Control><Alt>v"]) App().set_accels_for_action("app.shortcut::lyrics", ["<Control><Alt>l"]) App().set_accels_for_action("app.shortcut::next_album", ["<Control>n"]) App().set_accels_for_action("app.shortcut::current_artist", ["<Control><Alt>a"]) App().set_accels_for_action("app.shortcut::show_genres", ["<Control>g"]) App().set_accels_for_action("app.shortcut::show_sidebar", ["F9"]) App().set_accels_for_action("app.update_db", ["<Control>u"]) App().set_accels_for_action("app.settings", ["<Control>s"]) App().set_accels_for_action("app.fullscreen", ["F11", "F7"]) App().set_accels_for_action("app.mini", ["<Control>m"]) App().set_accels_for_action("app.about", ["F3"]) App().set_accels_for_action("app.shortcuts", ["F2"]) App().set_accels_for_action("app.help", ["F1"]) App().set_accels_for_action("app.quit", ["<Control>q"]) if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: App().set_accels_for_action("app.seek(10)", ["<Alt>Left"]) App().set_accels_for_action("app.seek(20)", ["<Control><Shift>Left"]) App().set_accels_for_action("app.seek(-10)", ["<Alt>Right"]) App().set_accels_for_action("app.seek(-20)", ["<Control><Shift>Right"]) else: App().set_accels_for_action("app.seek(10)", ["<Alt>Right"]) App().set_accels_for_action("app.seek(20)", ["<Control><Shift>Right"]) App().set_accels_for_action("app.seek(-10)", ["<Alt>Left"]) App().set_accels_for_action("app.seek(-20)", ["<Control><Shift>Left"]) App().set_accels_for_action("app.shortcut::play_pause", ["space", "<Alt>c"]) App().set_accels_for_action("app.shortcut::play", ["<Alt>x"]) App().set_accels_for_action("app.shortcut::stop", ["<Alt>v"]) App().set_accels_for_action("app.shortcut::next", ["<Alt>n"]) App().set_accels_for_action("app.shortcut::prev", ["<Alt>p"]) App().set_accels_for_action("app.shortcut::loved", ["<Alt>l"]) def __show_miniplayer(self, show): """ Show/hide subtoolbar @param show as bool """ if show and self.__miniplayer is None: from lollypop.miniplayer import MiniPlayer self.__miniplayer = MiniPlayer(self.get_size()[0]) self.__vgrid.add(self.__miniplayer) self.__toolbar.set_mini(True) elif not show and self.__miniplayer is not None: self.__toolbar.set_mini(False) self.__miniplayer.destroy() self.__miniplayer = None def __setup_size(self, size): """ Set window size @param size as (int, int) """ if len(size) == 2 and\ isinstance(size[0], int) and\ isinstance(size[1], int): self.resize(size[0], size[1]) def __setup_pos(self, pos): """ Set window position @param pos as (int, int) """ if len(pos) == 2 and\ isinstance(pos[0], int) and\ isinstance(pos[1], int): self.move(pos[0], pos[1]) # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __setup_media_keys(self): """ Setup media player keys """ self.__media_keys_busnames = [ "org.gnome.SettingDaemon.MediaKeys", "org.gnome.SettingsDaemon", ] self.__get_media_keys_proxy() # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __get_media_keys_proxy(self): if self.__media_keys_busnames: bus_name = self.__media_keys_busnames.pop(0) try: bus = App().get_dbus_connection() Gio.DBusProxy.new( bus, Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, None, bus_name, "/org/gnome/SettingsDaemon/MediaKeys", "org.gnome.SettingsDaemon.MediaKeys", None, self.__on_get_proxy, ) except Exception as e: Logger.error("Window::__setup_media_keys(): %s" % e) # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __on_get_proxy(self, source, result): try: self.__mediakeys = source.new_finish(result) except Exception as e: self.__mediakeys = None Logger.error("Window::__on_get_proxy(): %s" % e) else: if self.__mediakeys.get_name_owner(): self.__grab_media_keys() self.__mediakeys.connect('g-signal', self.__mediakey_signal) else: self.__mediakeys = None self.__get_media_keys_proxy() # FIXME Remove this, handled by MPRIS in GNOME 3.26 def __grab_media_keys(self): if not self.__mediakeys: return self.__mediakeys.call( "GrabMediaPlayerKeys", GLib.Variant("(su)", ("org.gnome.Lollypop", 0)), Gio.DBusCallFlags.NONE, -1, None, None, ) def __mediakey_signal(self, proxy, sender, signal, param, userdata=None): if signal != "MediaPlayerKeyPressed": return app, action = param.unpack() if app == "org.gnome.Lollypop": if action == "Play": App().player.play_pause() elif action == "Next": App().player.next() elif action == "Stop": App().player.stop() elif action == "Previous": App().player.prev() def __setup_content(self): """ Setup window content """ self.__container = Container() self.set_stack(self.container.stack) self.add_paned(self.container.paned_one, self.container.list_one) self.add_paned(self.container.paned_two, self.container.list_two) self.__container.show() self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_uri_targets() self.connect("drag-data-received", self.__on_drag_data_received) def __handle_miniplayer(self, width, height): """ Handle mini player show/hide @param width as int @param height as int """ if width < Sizing.MONSTER: self.__show_miniplayer(True) self.__miniplayer.set_vexpand(False) self.__container.stack.show() if self.__miniplayer is not None: self.__miniplayer.set_vexpand(False) else: self.__show_miniplayer(False) self.__container.stack.show() if self.__miniplayer is not None: self.__miniplayer.set_vexpand(False) if height < Sizing.MEDIUM and\ self.__miniplayer is not None and\ self.__miniplayer.is_visible(): self.__container.stack.hide() self.__miniplayer.set_vexpand(True) elif self.__miniplayer is not None: self.__container.stack.show() self.__miniplayer.set_vexpand(False) def __on_drag_data_received(self, widget, context, x, y, data, info, time): """ Import values @param widget as Gtk.Widget @param context as Gdk.DragContext @param x as int @param y as int @param data as Gtk.SelectionData @param info as int @param time as int """ try: from lollypop.collectionimporter import CollectionImporter from urllib.parse import urlparse importer = CollectionImporter() uris = [] for uri in data.get_text().strip("\n").split("\r"): parsed = urlparse(uri) if parsed.scheme in ["file", "sftp", "smb", "webdav"]: uris.append(uri) if uris: App().task_helper.run(importer.add, uris, callback=(App().scanner.update, )) except: pass def __on_map(self, window): """ Connect signals @param window as Window """ if self.__signal1 is None: self.__signal1 = self.connect("window-state-event", self.__on_window_state_event) if self.__signal2 is None: self.__signal2 = self.connect("configure-event", self.__on_configure_event) def __on_unmap(self, window): """ Disconnect signals @param window as Window """ if self.__signal1 is not None: self.disconnect(self.__signal1) self.__signal1 = None if self.__signal2 is not None: self.disconnect(self.__signal2) self.__signal2 = None def __on_configure_event(self, window, event): """ Handle configure event @param window as Gtk.Window @param event as Gdk.Event """ (width, height) = window.get_size() self.__handle_miniplayer(width, height) self.__toolbar.set_content_width(width) if self.__timeout_configure: GLib.source_remove(self.__timeout_configure) self.__timeout_configure = None if not self.is_maximized(): self.__timeout_configure = GLib.timeout_add( 1000, self.__save_size_position, window) def __save_size_position(self, widget): """ Save window state, update current view content size @param: widget as Gtk.Window """ self.__timeout_configure = None (width, height) = widget.get_size() if self.__miniplayer is not None: self.__miniplayer.update_cover(width) # Keep a minimal height if height < Sizing.MEDIUM: height = Sizing.MEDIUM App().settings.set_value("window-size", GLib.Variant("ai", [width, height])) (x, y) = widget.get_position() App().settings.set_value("window-position", GLib.Variant("ai", [x, y])) def __on_window_state_event(self, widget, event): """ Save maximised state """ App().settings.set_boolean( "window-maximized", "GDK_WINDOW_STATE_MAXIMIZED" in event.new_window_state.value_names) if event.changed_mask & Gdk.WindowState.FOCUSED and \ event.new_window_state & Gdk.WindowState.FOCUSED: # FIXME Remove this, handled by MPRIS in GNOME 3.26 self.__grab_media_keys() def __on_seek_action(self, action, param): """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ seconds = param.get_int32() position = App().player.position seek = position / Gst.SECOND + seconds if seek < 0: seek = 0 if seek > App().player.current_track.duration: seek = App().player.current_track.duration - 2 App().player.seek(seek) if App().player.current_track.id is not None: self.__toolbar.update_position(seek) def __on_player_action(self, action, param): """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ string = param.get_string() if string == "play_pause": App().player.play_pause() elif string == "play": App().player.play() elif string == "stop": App().player.stop() elif string == "next": App().player.next() elif string == "next_album": App().player.skip_album() elif string == "prev": App().player.prev() elif string == "locked": App().player.lock() elif string == "lyrics": App().window.container.show_lyrics() elif string == "show_sidebar": value = App().settings.get_value("show-sidebar") App().settings.set_value("show-sidebar", GLib.Variant("b", not value)) self.__container.show_sidebar(not value) elif string == "filter": if self.container.view is not None: self.container.view.enable_filter() elif string == "volume": if self.__miniplayer is None: self.__toolbar.title.show_hide_volume_control() else: self.__miniplayer.show_hide_volume_control() elif string == "current_artist": if App().player.current_track.id is not None and\ App().player.current_track.id > 0: artist_ids = App().player.current_track.album.artist_ids if App().settings.get_value("show-sidebar"): self.container.show_artists_albums(artist_ids) else: App().window.container.show_view(artist_ids[0]) elif string == "show_genres": state = not App().settings.get_value("show-genres") App().settings.set_value("show-genres", GLib.Variant("b", state)) self.__container.show_genres(state) elif string == "loved": track = App().player.current_track if track.id is not None and track.id >= 0: if track.loved < 1: loved = track.loved + 1 else: loved = Type.NONE track.set_loved(loved) if App().notify is not None: if track.loved == 1: heart = "❤" elif track.loved == -1: heart = "⏭" else: heart = "♡" App().notify.send( "%s - %s: %s" % (", ".join(track.artists), track.name, heart)) def __on_realize(self, widget): """ Run scanner on realize @param widget as Gtk.Widget """ self.setup() if App().settings.get_value("auto-update") or App().tracks.is_empty(): # Delayed, make python segfault on sys.exit() otherwise # No idea why, maybe scanner using Gstpbutils before Gstreamer # initialisation is finished... GLib.timeout_add(2000, App().scanner.update) # Here we ignore initial configure events self.__toolbar.set_content_width(self.get_size()[0]) def __on_current_changed(self, player): """ Update toolbar @param player as Player """ if App().player.current_track.id is None: self.set_title("Lollypop") else: artists = ", ".join(player.current_track.artists) self.set_title("%s - %s" % (artists, "Lollypop")) def __on_adaptive_changed(self, window, adaptive_stack): """ Handle adaptive mode @param window as AdaptiveWindow @param adaptive_stack as bool """ if adaptive_stack: self.__container.show_sidebar(True) self.__toolbar.end.set_mini(True) self.__container.list_one.add_value( (Type.SEARCH, _("Search"), _("Search"))) self.__container.list_one.add_value( (Type.CURRENT, _("Current playlist"), _("Current playlist"))) else: value = App().settings.get_value("show-sidebar") self.__toolbar.end.set_mini(False) self.__container.show_sidebar(value) self.__container.list_one.remove_value(Type.CURRENT) self.__container.list_one.remove_value(Type.SEARCH)
class Window(Gtk.ApplicationWindow, Container): """ Init window objects """ def __init__(self, app): Container.__init__(self) self._app = app Gtk.ApplicationWindow.__init__(self, application=app, title="Lollypop") self._nullwidget = Gtk.Label() # Use to get selected background color self._timeout_configure = None seek_action = Gio.SimpleAction.new('seek', GLib.VariantType.new('i')) seek_action.connect('activate', self._on_seek_action) app.add_action(seek_action) player_action = Gio.SimpleAction.new('player', GLib.VariantType.new('s')) player_action.connect('activate', self._on_player_action) app.add_action(player_action) self._setup_window() self._setup_media_keys() self.enable_global_shorcuts(True) self.connect("destroy", self._on_destroyed_window) """ Add an application menu to window @parma: menu as Gio.Menu """ def setup_menu(self, menu): self._toolbar.setup_menu_btn(menu) """ Return selected color """ def get_selected_color(self): return self._nullwidget.get_style_context( ).get_background_color(Gtk.StateFlags.SELECTED) """ Setup global shortcuts @param enable as bool """ def enable_global_shorcuts(self, enable): if enable: if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL: self._app.set_accels_for_action("app.seek(10)", ["Left"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Left"]) self._app.set_accels_for_action("app.seek(-10)", ["Right"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Right"]) else: self._app.set_accels_for_action("app.seek(10)", ["Right"]) self._app.set_accels_for_action("app.seek(20)", ["<Control>Right"]) self._app.set_accels_for_action("app.seek(-10)", ["Left"]) self._app.set_accels_for_action("app.seek(-20)", ["<Control>Left"]) self._app.set_accels_for_action("app.player::play_pause", ["space", "c"]) self._app.set_accels_for_action("app.player::play", ["x"]) self._app.set_accels_for_action("app.player::stop", ["v"]) else: self._app.set_accels_for_action("app.seek(10)", [None]) self._app.set_accels_for_action("app.seek(20)", [None]) self._app.set_accels_for_action("app.seek(-10)", [None]) self._app.set_accels_for_action("app.seek(-20)", [None]) self._app.set_accels_for_action("app.player::play_pause", [None]) self._app.set_accels_for_action("app.player::play", [None]) self._app.set_accels_for_action("app.player::stop", [None]) ############ # 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) self._toolbar = Toolbar(self.get_application()) self._toolbar.show() # Only set headerbar if according DE detected or forced manually if use_csd(): self.set_titlebar(self._toolbar) self._toolbar.set_show_close_button(True) self.add(self.main_widget()) else: hgrid = Gtk.Grid() hgrid.set_orientation(Gtk.Orientation.VERTICAL) hgrid.add(self._toolbar) hgrid.add(self.main_widget()) hgrid.show() self.add(hgrid) """ Delay event @param: widget as Gtk.Window @param: event as Gdk.Event """ def _on_configure_event(self, widget, event): self._toolbar.set_progress_width(widget.get_size()[0]/4) if self._timeout_configure: GLib.source_remove(self._timeout_configure) self._timeout_configure = 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_configure = 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())) """ Seek in stream @param action as Gio.SimpleAction @param param as GLib.Variant """ def _on_seek_action(self, action, param): seconds = param.get_int32() position = Objects.player.get_position_in_track() seek = position/1000000/60+seconds if seek < 0: seek = 0 if seek > Objects.player.current.duration: seek = Objects.player.current.duration - 2 Objects.player.seek(seek) self._toolbar.update_position(seek*60) """ Change player state @param action as Gio.SimpleAction @param param as GLib.Variant """ def _on_player_action(self, action, param): string = param.get_string() if string == "play_pause": Objects.player.play_pause() elif string == "play": Objects.player.play() elif string == "stop": Objects.player.stop()