%PDF- %PDF-
Direktori : /usr/lib/rhythmbox/plugins/alternative-toolbar/ |
Current File : //usr/lib/rhythmbox/plugins/alternative-toolbar/alttoolbar_repeat.py |
# This is a part of the external Repeat One Song plugin for Rhythmbox # # Author: Eduardo Mucelli Rezende Oliveira # E-mail: edumucelli@gmail.com or eduardom@dcc.ufmg.br # Version: 0.4 (Unstable) for Rhythmbox 3.0.1 or later # # reworked for alternative-toolbar # Author: David Mohammed 2015-2020 <fossfreedom@ubuntu.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. from alttoolbar_preferences import CoverLocale from alttoolbar_preferences import GSetting from gi.repository import GLib from gi.repository import GObject from gi.repository import Gdk from gi.repository import Gio from gi.repository import Gtk class Repeat(GObject.Object): """ Object handling song repeating, with an additional feature of repeating one song, one song only. """ SONG_CHANGED_MANUAL = 0 SONG_CHANGED_EOS = 1 def __init__(self, shell, toggle_button): """ :param shell: the plugin object :param toggle_button: button that controls the repeat functions """ GObject.Object.__init__(self) # use this to start the repeat-one-song capability (if True) self.repeat_song = False self.toggle_button = toggle_button self.song_changed = self.SONG_CHANGED_MANUAL player = shell.props.shell_player # EOS signal means that the song changed because the song is over. # ie. the user did not manually change the song. # https://developer.gnome.org/rhythmbox/unstable/RBPlayer.html#RBPlayer-eos player.props.player.connect('eos', self.on_gst_player_eos) player.connect('playing-song-changed', self.on_song_change) # This hack is no longer needed when the above signal handlers # work. For more details, refer to the comments above the # definition of method on_elapsed_change. # player.connect('elapsed-changed', self.on_elapsed_change) try: popover = Gtk.Popover.new(toggle_button) except AttributeError: # use our custom Popover equivalent for Gtk+3.10 folks popover = CustomPopover(toggle_button) else: popover.set_modal(False) finally: repeat = RepeatPopContainer(popover, toggle_button) popover.add(repeat) toggle_button.connect('toggled', self._on_toggle, popover, repeat) repeat.connect('repeat-type-changed', self._on_repeat_type_changed) self._on_repeat_type_changed(repeat, repeat.get_repeat_type()) def _on_toggle(self, toggle, popover, repeat): if toggle.get_active(): popover.show_all() self.repeat_song = \ repeat.get_repeat_type() == RepeatPopContainer.ONE_SONG else: popover.hide() self.repeat_song = False self._set_toggle_tooltip(repeat) print("on toggle", self.repeat_song) def _set_toggle_tooltip(self, repeat): # locale stuff cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) if self.toggle_button.get_has_tooltip(): if repeat.get_repeat_type() == RepeatPopContainer.ALL_SONGS: message = _("Repeat all tracks") else: message = _("Repeat the current track") self.toggle_button.set_tooltip_text(message) cl = CoverLocale() cl.switch_locale(cl.Locale.RB) def _on_repeat_type_changed(self, repeat, repeat_type): if self.toggle_button.get_active(): if repeat_type == RepeatPopContainer.ONE_SONG: self.repeat_song = True else: self.repeat_song = False else: self.repeat_song = False self._set_toggle_tooltip(repeat) print("repeat type changed", self.repeat_song) def on_gst_player_eos(self, gst_player, stream_data, early=0): """ Set song_changed to SONG_CHANGED_EOS so that on_song_change will know to repeat the song. """ if self.repeat_song: self.song_changed = self.SONG_CHANGED_EOS def on_song_change(self, player, time): """ Repeat song that has just been played (when called on song change signal). """ if self.song_changed == self.SONG_CHANGED_EOS: self.song_changed = self.SONG_CHANGED_MANUAL player.do_previous() # Since seg faults no longer seem to happen when the 'eos' callback # is called with GStreamer 1.0, on_gst_player_eos in conjunction # with on_song_change are used instead of this method to control the # song repetition. The related to GStreamer is described at # https://bugs.launchpad.net/ubuntu/+source/rhythmbox/+bug/1239218 def on_elapsed_change(self, player, time): """ This is a old method to 'repeat' the current song as soon as it reaches the last seconds. """ if self.repeat_song: # This might be improved by keeping a instance variable with # the duration and updating it on_song_change in order to # avoid querying the duration on every call. duration = player.get_playing_song_duration() if duration > 0: # Repeat on the last two seconds of the song. Previously the # last second was used but RB now seems to use the last second # to prepare things for the next song of the list. if time >= duration - 2: player.set_playing_time(0) class RepeatPopContainer(Gtk.ButtonBox): __gsignals__ = { "repeat-type-changed": (GObject.SIGNAL_RUN_LAST, None, (int,)) } # repeat-type-changed is emitted with one of the following values ONE_SONG = 1 ALL_SONGS = 2 def __init__(self, parent_container, parent_button, *args, **kwargs): super(RepeatPopContainer, self).__init__(*args, **kwargs) self.set_orientation(Gtk.Orientation.HORIZONTAL) self.set_layout(Gtk.ButtonBoxStyle.START) self.props.margin = 5 context = self.get_style_context() context.add_class('linked') icon_size = 4 toggle1 = Gtk.RadioButton.new(None) toggle1.set_mode(False) fallback = 'media-playlist-repeat-symbolic' icon = Gio.ThemedIcon.new_with_default_fallbacks(fallback) image = Gtk.Image() image.set_from_gicon(icon, icon_size) image.props.margin = 5 toggle1.set_image(image) toggle1.connect('leave-notify-event', self._on_popover_mouse_over) toggle1.connect('enter-notify-event', self._on_popover_mouse_over) toggle1.connect('toggled', self._on_popover_button_toggled) # locale stuff cl = CoverLocale() cl.switch_locale(cl.Locale.LOCALE_DOMAIN) if parent_button.get_has_tooltip(): toggle1.set_tooltip_text(_("Repeat all tracks")) self._repeat_button = toggle1 self.add(toggle1) self.child_set_property(toggle1, "non-homogeneous", True) toggle1.show_all() self._repeat_image = Gtk.Image() self._repeat_image.set_from_gicon(icon, icon_size) self._repeat_image.props.margin = 5 toggle2 = Gtk.RadioButton.new_from_widget(toggle1) toggle2.set_mode(False) sym = 'media-playlist-repeat-song-symbolic' icon2 = Gio.ThemedIcon.new_with_default_fallbacks(sym) image2 = Gtk.Image() image2.set_from_gicon(icon2, icon_size) image2.props.margin = 5 toggle2.set_image(image2) if parent_button.get_has_tooltip(): toggle2.set_tooltip_text(_("Repeat the current track")) self._repeat_song_image = Gtk.Image() self._repeat_song_image.set_from_gicon(icon2, icon_size) self._repeat_song_image.props.margin = 5 toggle2.connect('leave-notify-event', self._on_popover_mouse_over) toggle2.connect('enter-notify-event', self._on_popover_mouse_over) toggle2.connect('toggled', self._on_popover_button_toggled) toggle2.show_all() self._repeat_song_button = toggle2 self.add(toggle2) self.child_set_property(toggle2, "non-homogeneous", True) self._popover_inprogress = 0 parent_container.connect('leave-notify-event', self._on_popover_mouse_over) parent_container.connect('enter-notify-event', self._on_popover_mouse_over) parent_button.connect('leave-notify-event', self._on_popover_mouse_over) parent_button.connect('enter-notify-event', self._on_popover_mouse_over) parent_button.set_image(self._repeat_image) self._parent_container = parent_container self._parent_button = parent_button # now get the repeat-type saved in gsettings # get values from gsettings self.gs = GSetting() self.plugin_settings = self.gs.get_setting(self.gs.Path.PLUGIN) repeat_type = self.plugin_settings[self.gs.PluginKey.REPEAT_TYPE] if repeat_type == RepeatPopContainer.ONE_SONG: self._repeat_song_button.set_active(True) def _on_popover_button_toggled(self, button, *args): print("popover toggle") if button.get_active(): if button == self._repeat_button: self._parent_button.set_image(self._repeat_image) self.emit('repeat-type-changed', RepeatPopContainer.ALL_SONGS) self.plugin_settings[self.gs.PluginKey.REPEAT_TYPE] = \ RepeatPopContainer.ALL_SONGS else: self._parent_button.set_image(self._repeat_song_image) self.emit('repeat-type-changed', RepeatPopContainer.ONE_SONG) self.plugin_settings[self.gs.PluginKey.REPEAT_TYPE] = \ RepeatPopContainer.ONE_SONG def get_repeat_type(self): repeat_type = RepeatPopContainer.ALL_SONGS if self._repeat_song_button.get_active(): repeat_type = RepeatPopContainer.ONE_SONG return repeat_type def _on_popover_mouse_over(self, widget, eventcrossing): if eventcrossing.type == Gdk.EventType.ENTER_NOTIFY: if self._popover_inprogress == 0: self._popover_inprogress = 1 print("enter1") else: self._popover_inprogress = 2 print("enter2") self._popover_inprogress_count = 0 if type(widget) is Gtk.ToggleButton: print("here") if widget.get_active(): print(self._parent_container) self._parent_container.show_all() else: print("exit") self._popover_inprogress = 3 def delayed(*args): if self._popover_inprogress == 3: self._popover_inprogress_count += 1 if self._popover_inprogress_count < 5: return True self._parent_container.hide() self._popover_inprogress = 0 print("exit timeout") return False else: return True if self._popover_inprogress == 1: print("adding timeout") self._popover_inprogress = 2 GLib.timeout_add(100, delayed) class CustomPopover(Gtk.Window): def __init__(self, parent_button, *args, **kwargs): super(CustomPopover, self).__init__(type=Gtk.WindowType.POPUP, *args, **kwargs) self.set_decorated(False) self.set_resizable(False) self.set_type_hint(Gdk.WindowTypeHint.DOCK) self.stick() self._parent_button = parent_button self.connect_after('show', self._on_show) # Track movements of the window to move calendar window as well self.connect("configure-event", self.on_window_config) def add(self, widget): self._frame = Gtk.Frame() self._frame.add(widget) super(CustomPopover, self).add(self._frame) self._frame.show_all() # Popoverwindow co ordinates without off-screen correction: # Window origin (x, y) # | # V # --------------------------------- # | Main Window | # | | # | | # |Toggle button's (x, y) | # |(relative to parent window) | # | | | # | V | # | ......................... | # Popover | | Toggle Button | | # window's | | | | # (x, y)---+> ......................... | # |(window will be here) | # | | # | | # --------------------------------- # Popover Window's screen coordinates: # x = Window's origin x + Toggle Button's relative x # y = Window's origin y + Toggle Button's relative y + Toggle Button's # height def _on_show(self, widget): rect = self._parent_button.get_allocation() main_window = self._parent_button.get_toplevel() [val, win_x, win_y] = main_window.get_window().get_origin() cal_x = win_x + rect.x cal_y = win_y + rect.y + rect.height [x, y] = self.apply_screen_coord_correction(cal_x, cal_y) self.move(x, y) # This function "tries" to correct calendar window position so that it is # not obscured when # a portion of main window is off-screen. # Known bug: If the main window is partially off-screen before Calendar # window # has been realized then get_allocation() will return rect of 1x1 in which # case # the calculations will fail & correction will not be applied def apply_screen_coord_correction(self, x, y): corrected_y = y corrected_x = x rect = self.get_allocation() screen_w = Gdk.Screen.width() screen_h = Gdk.Screen.height() delta_x = screen_w - (x + rect.width) delta_y = screen_h - (y + rect.height) if delta_x < 0: corrected_x += delta_x print("at x") if corrected_x < 0: corrected_x = 0 button_rect = self._parent_button.get_allocation() window_width, window_height = \ self._parent_button.get_toplevel().get_size() # print (y, button_rect.y, button_rect.height, ) calc = (window_height - (button_rect.y + (button_rect.height * 2))) if delta_y < 0 or (calc < 0): btn_hgt = self._parent_button.get_allocation().height corrected_y = y - rect.height - btn_hgt print("at y") if corrected_y < 0: corrected_y = 0 return [corrected_x, corrected_y] # "configure-event" callback of main window, try to move calendar window # along with main window. def on_window_config(self, widget, event): # Maybe better way to find the visiblilty if self.get_mapped(): rect = self._parent_button.get_allocation() main_window = self._parent_button.get_toplevel() [val, win_x, win_y] = main_window.get_window().get_origin() cal_x = win_x + rect.x cal_y = win_y + rect.y + rect.height self.show_all() [x, y] = self.apply_screen_coord_correction(cal_x, cal_y) self.move(x, y)