Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
 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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
 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())
Exemplo n.º 6
0
	def _setup_view(self):
		self._paned_main_list = Gtk.HPaned()
		self._paned_list_view = Gtk.HPaned()
		vgrid = Gtk.Grid()
		vgrid.set_orientation(Gtk.Orientation.VERTICAL)
	
		self._toolbar = Toolbar()
		self._toolbar.header_bar.show()
		self._toolbar.get_view_genres_btn().connect("toggled", self._setup_list_one)

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

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

		self._progress = Gtk.ProgressBar()

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

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

		separator = Gtk.Separator()
		separator.show()
		self._paned_list_view.add1(self._list_two.widget)
		self._paned_list_view.add2(vgrid)
		self._paned_main_list.add1(self._list_one.widget)
		self._paned_main_list.add2(self._paned_list_view)
		self._paned_main_list.set_position(Objects["settings"].get_value("paned-mainlist-width").get_int32())
		self._paned_list_view.set_position(Objects["settings"].get_value("paned-listview-width").get_int32())
		self._paned_main_list.show()
		self._paned_list_view.show()
		self.show()
Exemplo n.º 7
0
 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')
Exemplo n.º 8
0
 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")
Exemplo n.º 9
0
 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)
Exemplo n.º 10
0
 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)
Exemplo n.º 11
0
 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())
Exemplo n.º 12
0
	def _setup_view(self):
		self._box = Gtk.Grid()
		self._toolbar = Toolbar(self._db, self._player)
		self.set_titlebar(self._toolbar.header_bar)
		self._toolbar.header_bar.show()
		self._toolbar.get_infobox().connect("button-press-event", self._show_current_album)

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

		separator = Gtk.Separator()
		separator.show()
		self._box.add(self._list_genres.widget)
		self._box.add(separator)
		self._box.add(self._list_artists.widget)
		self._box.add(self._view)
		self.add(self._box)
		self._box.show()
		self.show()
Exemplo n.º 13
0
 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')
Exemplo n.º 14
0
class Window(Gtk.ApplicationWindow):

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

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

		self._setup_window()				
		self._setup_view()

		self._setup_media_keys()

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

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

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

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


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

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

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

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

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

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

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

	"""
		Setup window main view:
			- genre list
			- artist list
			- main view as artist view or album view
	"""
	def _setup_view(self):
		self._paned_main_list = Gtk.HPaned()
		self._paned_list_view = Gtk.HPaned()
		vgrid = Gtk.Grid()
		vgrid.set_orientation(Gtk.Orientation.VERTICAL)
	
		self._toolbar = Toolbar()
		self._toolbar.header_bar.show()
		self._toolbar.get_view_genres_btn().connect("toggled", self._setup_list_one)

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

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

		self._progress = Gtk.ProgressBar()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	"""
		Save paned widget width
		@param widget as unused, data as unused
	"""	
	def _on_destroyed_window(self, widget):
		Objects["settings"].set_value("paned-mainlist-width", GLib.Variant('i', self._paned_main_list.get_position()))
		Objects["settings"].set_value("paned-listview-width", GLib.Variant('i', self._paned_list_view.get_position()))
Exemplo n.º 15
0
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")
Exemplo n.º 16
0
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()
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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")
Exemplo n.º 19
0
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")
Exemplo n.º 20
0
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)
Exemplo n.º 21
0
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)
Exemplo n.º 22
0
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()
Exemplo n.º 23
0
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()
Exemplo n.º 24
0
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)
Exemplo n.º 25
0
class Window(Gtk.ApplicationWindow):

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

		self._artist_signal_id = 0

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	"""
		Show current album context/content
	"""
	def _show_current_album(self, obj, data):
		track_id = self._player.get_current_track_id()
		if  track_id != -1:
			self._view.current_changed(False, track_id)
Exemplo n.º 26
0
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)
Exemplo n.º 27
0
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()