class BaseTrayIcon(object): """ Trayicon base, needs to be derived from """ def __init__(self, main): self.main = main self.VOLUME_STEP = 0.05 self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) self.tooltip.set_display_progress(True) self.menu = menu.ProviderMenu('tray-icon-context', self) self.update_icon() self.connect_events() event.log_event('tray_icon_toggled', self, True) def destroy(self): """ Unhides the window and removes the tray icon """ # FIXME: Allow other windows too if not self.main.window.get_property('visible'): self.main.window.deiconify() self.main.window.present() self.disconnect_events() self.set_visible(False) self.tooltip.destroy() event.log_event('tray_icon_toggled', self, False) def connect_events(self): """ Connects various callbacks with events """ self.connect('button-press-event', self.on_button_press_event) self.connect('scroll-event', self.on_scroll_event) event.add_ui_callback(self.on_playback_change_state, 'playback_player_end', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_track_start', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_error', player.PLAYER) def disconnect_events(self): """ Disconnects various callbacks from events """ event.remove_callback(self.on_playback_change_state, 'playback_player_end', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_track_start', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_error', player.PLAYER) def update_icon(self): """ Updates icon appearance based on current playback state """ if player.PLAYER.current is None: self.set_from_icon_name('exaile') self.set_tooltip(_('Exaile Music Player')) elif player.PLAYER.is_paused(): self.set_from_icon_name('exaile-pause') else: self.set_from_icon_name('exaile-play') def set_from_icon_name(self, icon_name): """ Updates the tray icon """ pass def set_tooltip(self, tooltip_text): """ Updates the tray icon tooltip """ pass def set_visible(self, visible): """ Shows or hides the tray icon """ pass def get_menu_position(self, menu, icon): """ Returns coordinates for the best menu position """ return (0, 0, False) def on_button_press_event(self, widget, event): """ Toggles main window visibility and pause as well as opens the context menu """ if event.button == Gdk.BUTTON_PRIMARY: self.main.toggle_visible(bringtofront=True) if event.button == Gdk.BUTTON_MIDDLE: playback.playpause(player.PLAYER) if event.triggers_context_menu(): self.menu.popup(None, None, self.get_menu_position, self, event.button, event.time) def on_scroll_event(self, widget, event): """ Changes volume and skips tracks on scroll """ if event.get_state() & Gdk.ModifierType.SHIFT_MASK: if event.direction == Gdk.ScrollDirection.UP: player.QUEUE.prev() elif event.direction == Gdk.ScrollDirection.DOWN: player.QUEUE.next() else: if event.direction == Gdk.ScrollDirection.UP: volume = settings.get_option('player/volume', 1) settings.set_option('player/volume', min(volume + self.VOLUME_STEP, 1)) return True elif event.direction == Gdk.ScrollDirection.DOWN: volume = settings.get_option('player/volume', 1) settings.set_option('player/volume', max(0, volume - self.VOLUME_STEP)) return True elif event.direction == Gdk.ScrollDirection.LEFT: player.QUEUE.prev() elif event.direction == Gdk.ScrollDirection.RIGHT: player.QUEUE.next() def on_playback_change_state(self, event, player, current): """ Updates tray icon appearance on playback state change """ self.update_icon()
class ProgressButtonControl(PlaylistButtonControl): name = 'progress_button' title = _('Progress button') description = _('Playback progress and access ' 'to the current playlist') # Required to make overrides work __gsignals__ = {} def __init__(self): PlaylistButtonControl.__init__(self) self.set_name('progressbutton') self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.progressbar = SeekProgressBar(player.PLAYER) self.progressbar.set_size_request(-1, 1) self.progressbar.formatter = ProgressButtonFormatter() self.progressbar.set_text = lambda *a: None # Needed by PlaylistButtonControl gtk_widget_replace(self.label, self.progressbar) self.label = self.progressbar if player.PLAYER.current is not None: self.progressbar.on_playback_track_start('playback_track_start', player.PLAYER, player.PLAYER.current) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) def destroy(self): """ Cleanups """ self.tooltip.destroy() PlaylistButtonControl.destroy(self) def do_button_press_event(self, event): """ Trigger normal toggle action or seek """ if event.button == 1: PlaylistButtonControl.do_button_press_event(self, event) elif event.button == 2: event = event.copy() event.button = 1 x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('button-press-event', event) def do_button_release_event(self, event): if event.button == 1: PlaylistButtonControl.do_button_release_event(self, event) elif event.button == 2: event = event.copy() event.button = 1 x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('button-release-event', event) def do_motion_notify_event(self, event): event = event.copy() x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('motion-notify-event', event) def do_leave_notify_event(self, event): event = event.copy() x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('leave-notify-event', event)
class PlaylistButtonControl(Gtk.ToggleButton, BaseControl, QueueAdapter): name = 'playlist_button' title = _('Playlist button') description = _('Access the current playlist') __gsignals__ = {'scroll-event': 'override'} def __init__(self): Gtk.ToggleButton.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.set_focus_on_click(False) self.set_size_request(200, -1) box = Gtk.Box() self.arrow = Gtk.Arrow(Gtk.ArrowType.RIGHT, Gtk.ShadowType.OUT) box.pack_start(self.arrow, False, True, 0) self.label = Gtk.Label(label='') self.label.props.ellipsize = Pango.EllipsizeMode.END box.pack_start(self.label, True, True, 0) self.add(box) self.formatter = TrackFormatter( settings.get_option('plugin/minimode/track_title_format', '$tracknumber - $title')) self.view = PlaylistView(player.QUEUE.current_playlist, player.PLAYER) self.popup = AttachedWindow(self) self.popup.set_default_size( settings.get_option( 'plugin/minimode/' 'playlist_button_popup_width', 350), settings.get_option( 'plugin/minimode/' 'playlist_button_popup_height', 400)) scrollwindow = Gtk.ScrolledWindow() scrollwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrollwindow.set_shadow_type(Gtk.ShadowType.IN) scrollwindow.add(self.view) self.popup.add(scrollwindow) self.popup.connect('show', self.on_popup_show) self.popup.connect('hide', self.on_popup_hide) self.popup.connect('configure-event', self.on_popup_configure_event) accel_group = Gtk.AccelGroup() key, modifier = Gtk.accelerator_parse('<Control>J') accel_group.connect(key, modifier, Gtk.AccelFlags.VISIBLE, self.on_accelerator_activate) self.popup.add_accel_group(accel_group) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) if player.PLAYER.current is not None: self.label.set_text(self.formatter.format(player.PLAYER.current)) self._drag_motion_timeout_id = None self._drag_leave_timeout_id = None self.drag_dest_set( Gtk.DestDefaults.ALL, self.view.targets, Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-data-received', self.on_drag_data_received) self.view.connect('drag-motion', self.on_drag_motion) self.view.connect('drag-leave', self.on_drag_leave) event.add_ui_callback(self.on_track_tags_changed, 'track_tags_changed') event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set') self.on_option_set('plugin_minimode_option_set', settings, 'plugin/minimode/track_title_format') def destroy(self): """ Cleanups """ self.tooltip.destroy() QueueAdapter.destroy(self) Gtk.ToggleButton.destroy(self) def update_playlist(self, playlist): """ Updates the internally stored playlist """ columns = self.view.get_model().columns model = PlaylistModel(playlist, columns, player.PLAYER) self.view.set_model(model) def do_scroll_event(self, event): """ Changes the current track """ if event.direction == Gdk.ScrollDirection.UP: self.view.playlist.prev() elif event.direction == Gdk.ScrollDirection.DOWN: self.view.playlist.next() else: return position = self.view.playlist.current_position try: track = self.view.playlist[position] except IndexError: pass else: player.QUEUE.play(track) def do_toggled(self): """ Shows or hides the playlist """ if self.get_active(): self.arrow.props.arrow_type = Gtk.ArrowType.DOWN self.popup.show_all() else: self.popup.hide() self.arrow.props.arrow_type = Gtk.ArrowType.RIGHT def on_accelerator_activate(self, accel_group, acceleratable, keyval, modifier): """ Shows the current track """ self.view.scroll_to_cell(self.view.playlist.current_position) self.view.set_cursor(self.view.playlist.current_position) def on_drag_motion(self, widget, context, x, y, time): """ Prepares to show the playlist """ # Defer display of the playlist if self._drag_motion_timeout_id is None: self._drag_motion_timeout_id = GLib.timeout_add( 500, lambda: self.set_active(True)) # Prevent hiding of the playlist if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None def on_drag_leave(self, widget, context, time): """ Prepares to hide the playlist """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: GLib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) # Defer hiding of the playlist self._drag_leave_timeout_id = GLib.timeout_add( 500, lambda: self.set_active(False)) def on_drag_data_received(self, widget, context, x, y, selection, info, time): """ Handles dropped data """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: GLib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None # Enable hiding of the playlist on re-enter if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None self.view.emit('drag-data-received', context, x, y, selection, info, time) def on_popup_show(self, widget): if not self.get_active(): self.set_active(True) def on_popup_hide(self, widget): if self.get_active(): self.set_active(False) def on_popup_configure_event(self, widget, event): """ Saves the window size after resizing """ width = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_width', 350) height = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_height', 400) if event.width != width: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_width', event.width) if event.height != height: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_height', event.height) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ GLib.idle_add(self.update_playlist, playlist) def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ try: track = playlist[positions[0]] except IndexError: text = '' else: text = self.formatter.format(track) GLib.idle_add(self.label.set_text, text) def on_track_tags_changed(self, event, track, tag): """ Updates the button on tag changes """ playlist = self.view.playlist track_position = playlist.index(track) if track in playlist and track_position == playlist.current_position: self.label.set_text(self.formatter.format(track)) def on_option_set(self, event, settings, option): """ Updates control upon setting change """ if option == 'plugin/minimode/track_title_format': self.formatter.set_property( 'format', settings.get_option(option, _('$tracknumber - $title')))
class PlaylistButtonControl(Gtk.ToggleButton, BaseControl, QueueAdapter): name = 'playlist_button' title = _('Playlist button') description = _('Access the current playlist') __gsignals__ = {'scroll-event': 'override'} def __init__(self): Gtk.ToggleButton.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.set_focus_on_click(False) self.set_size_request(200, -1) box = Gtk.Box() self.arrow = Gtk.Arrow(Gtk.ArrowType.RIGHT, Gtk.ShadowType.OUT) box.pack_start(self.arrow, False, True, 0) self.label = Gtk.Label(label='') self.label.props.ellipsize = Pango.EllipsizeMode.END box.pack_start(self.label, True, True, 0) self.add(box) self.formatter = TrackFormatter( settings.get_option( 'plugin/minimode/track_title_format', '$tracknumber - $title' ) ) self.view = PlaylistView(player.QUEUE.current_playlist, player.PLAYER) self.popup = AttachedWindow(self) self.popup.set_default_size( settings.get_option('plugin/minimode/' 'playlist_button_popup_width', 350), settings.get_option('plugin/minimode/' 'playlist_button_popup_height', 400), ) scrollwindow = Gtk.ScrolledWindow() scrollwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrollwindow.set_shadow_type(Gtk.ShadowType.IN) scrollwindow.add(self.view) self.popup.add(scrollwindow) self.popup.connect('show', self.on_popup_show) self.popup.connect('hide', self.on_popup_hide) self.popup.connect('configure-event', self.on_popup_configure_event) accel_group = Gtk.AccelGroup() key, modifier = Gtk.accelerator_parse('<Primary>J') accel_group.connect( key, modifier, Gtk.AccelFlags.VISIBLE, self.on_accelerator_activate ) self.popup.add_accel_group(accel_group) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) if player.PLAYER.current is not None: self.label.set_text(self.formatter.format(player.PLAYER.current)) self._drag_motion_timeout_id = None self._drag_leave_timeout_id = None self.drag_dest_set( Gtk.DestDefaults.ALL, self.view.targets, Gdk.DragAction.COPY | Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE, ) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-data-received', self.on_drag_data_received) self.view.connect('drag-motion', self.on_drag_motion) self.view.connect('drag-leave', self.on_drag_leave) event.add_ui_callback(self.on_track_tags_changed, 'track_tags_changed') event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set') self.on_option_set( 'plugin_minimode_option_set', settings, 'plugin/minimode/track_title_format' ) def destroy(self): """ Cleanups """ self.tooltip.destroy() QueueAdapter.destroy(self) Gtk.ToggleButton.destroy(self) def update_playlist(self, playlist): """ Updates the internally stored playlist """ columns = self.view.model.column_names model = PlaylistModel(playlist, columns, player.PLAYER, self.view) self.view.set_model(model) def do_scroll_event(self, event): """ Changes the current track """ if event.direction == Gdk.ScrollDirection.UP: self.view.playlist.prev() elif event.direction == Gdk.ScrollDirection.DOWN: self.view.playlist.next() else: return position = self.view.playlist.current_position try: track = self.view.playlist[position] except IndexError: pass else: player.QUEUE.play(track) def do_toggled(self): """ Shows or hides the playlist """ if self.get_active(): self.arrow.props.arrow_type = Gtk.ArrowType.DOWN self.popup.show_all() else: self.popup.hide() self.arrow.props.arrow_type = Gtk.ArrowType.RIGHT def on_accelerator_activate(self, accel_group, acceleratable, keyval, modifier): """ Shows the current track """ self.view.scroll_to_cell(self.view.playlist.current_position) self.view.set_cursor(self.view.playlist.current_position) def on_drag_motion(self, widget, context, x, y, time): """ Prepares to show the playlist """ # Defer display of the playlist if self._drag_motion_timeout_id is None: self._drag_motion_timeout_id = GLib.timeout_add( 500, lambda: self.set_active(True) ) # Prevent hiding of the playlist if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None def on_drag_leave(self, widget, context, time): """ Prepares to hide the playlist """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: GLib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) # Defer hiding of the playlist self._drag_leave_timeout_id = GLib.timeout_add( 500, lambda: self.set_active(False) ) def on_drag_data_received(self, widget, context, x, y, selection, info, time): """ Handles dropped data """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: GLib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None # Enable hiding of the playlist on re-enter if self._drag_leave_timeout_id is not None: GLib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None self.view.emit('drag-data-received', context, x, y, selection, info, time) def on_popup_show(self, widget): if not self.get_active(): self.set_active(True) def on_popup_hide(self, widget): if self.get_active(): self.set_active(False) def on_popup_configure_event(self, widget, event): """ Saves the window size after resizing """ width = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_width', 350 ) height = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_height', 400 ) if event.width != width: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_width', event.width ) if event.height != height: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_height', event.height ) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ GLib.idle_add(self.update_playlist, playlist) def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ try: track = playlist[positions[0]] except IndexError: text = '' else: text = self.formatter.format(track) GLib.idle_add(self.label.set_text, text) def on_track_tags_changed(self, event, track, tags): """ Updates the button on tag changes """ playlist = self.view.playlist if track not in playlist: return track_position = playlist.index(track) if track_position == playlist.current_position: self.label.set_text(self.formatter.format(track)) def on_option_set(self, event, settings, option): """ Updates control upon setting change """ if option == 'plugin/minimode/track_title_format': self.formatter.set_property( 'format', settings.get_option(option, _('$tracknumber - $title')) )
class ProgressButtonControl(PlaylistButtonControl): name = 'progress_button' title = _('Progress button') description = _('Playback progress and access ' 'to the current playlist') # Required to make overrides work __gsignals__ = {} def __init__(self): PlaylistButtonControl.__init__(self) self.set_name('progressbutton') self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.progressbar = SeekProgressBar(player.PLAYER) self.progressbar.set_size_request(-1, 1) self.progressbar.formatter = ProgressButtonFormatter() self.progressbar.set_text = lambda *a: None # Needed by PlaylistButtonControl gtk_widget_replace(self.label, self.progressbar) self.label = self.progressbar if player.PLAYER.current is not None: self.progressbar.on_playback_track_start( 'playback_track_start', player.PLAYER, player.PLAYER.current ) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) def destroy(self): """ Cleanups """ self.tooltip.destroy() PlaylistButtonControl.destroy(self) def do_button_press_event(self, event): """ Trigger normal toggle action or seek """ if event.button == Gdk.BUTTON_PRIMARY: PlaylistButtonControl.do_button_press_event(self, event) elif event.button == Gdk.BUTTON_MIDDLE: event = event.copy() event.button = Gdk.BUTTON_PRIMARY x, y = self.translate_coordinates( self.progressbar, int(event.x), int(event.y) ) event.x, event.y = float(x), float(y) self.progressbar.emit('button-press-event', event) def do_button_release_event(self, event): if event.button == Gdk.BUTTON_PRIMARY: PlaylistButtonControl.do_button_release_event(self, event) elif event.button == Gdk.BUTTON_MIDDLE: event = event.copy() event.button = Gdk.BUTTON_PRIMARY x, y = self.translate_coordinates( self.progressbar, int(event.x), int(event.y) ) event.x, event.y = float(x), float(y) self.progressbar.emit('button-release-event', event) def do_motion_notify_event(self, event): event = event.copy() x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('motion-notify-event', event) def do_leave_notify_event(self, event): event = event.copy() x, y = self.translate_coordinates(self.progressbar, int(event.x), int(event.y)) event.x, event.y = float(x), float(y) self.progressbar.emit('leave-notify-event', event)
class PlaylistButtonControl(gtk.ToggleButton, BaseControl, QueueAdapter): name = 'playlist_button' title = _('Playlist button') description = _('Access the current playlist') __gsignals__ = {'scroll-event': 'override'} def __init__(self): gtk.ToggleButton.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.set_focus_on_click(False) self.set_size_request(200, -1) box = gtk.HBox() self.arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_OUT) box.pack_start(self.arrow, expand=False) self.label = gtk.Label('') self.label.props.ellipsize = pango.ELLIPSIZE_END box.pack_start(self.label) self.add(box) self.formatter = TrackFormatter( settings.get_option('plugin/minimode/track_title_format', '$tracknumber - $title')) self.view = PlaylistView(player.QUEUE.current_playlist, player.PLAYER) self.popup = AttachedWindow(self) self.popup.set_default_size( settings.get_option('plugin/minimode/' 'playlist_button_popup_width', 350), settings.get_option('plugin/minimode/' 'playlist_button_popup_height', 400) ) scrollwindow = gtk.ScrolledWindow() scrollwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrollwindow.set_shadow_type(gtk.SHADOW_IN) scrollwindow.add(self.view) self.popup.add(scrollwindow) self.popup.connect('configure-event', self.on_popup_configure_event) accel_group = gtk.AccelGroup() key, modifier = gtk.accelerator_parse('<Control>J') accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.on_accelerator_activate) self.popup.add_accel_group(accel_group) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) if player.PLAYER.current is not None: self.label.set_text(self.formatter.format(player.PLAYER.current)) self._drag_motion_timeout_id = None self._drag_leave_timeout_id = None self._toplevel_hide_id = None self._toplevel_window_state_event_id = None self.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.view.targets, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-data-received', self.on_drag_data_received) self.view.connect('drag-motion', self.on_drag_motion) self.view.connect('drag-leave', self.on_drag_leave) event.add_callback(self.on_track_tags_changed, 'track_tags_changed') event.add_callback(self.on_option_set, 'plugin_minimode_option_set') self.on_option_set('plugin_minimode_option_set', settings, 'plugin/minimode/track_title_format') def destroy(self): """ Cleanups """ self.tooltip.destroy() QueueAdapter.destroy(self) gtk.ToggleButton.destroy(self) def update_playlist(self, playlist): """ Updates the internally stored playlist """ columns = self.view.get_model().columns model = PlaylistModel(playlist, columns, player.PLAYER) self.view.set_model(model) def do_hierarchy_changed(self, previous_toplevel): """ Sets up automatic hiding on parent hide """ if self._toplevel_hide_id is not None: previous_toplevel.disconnect( self._toplevel_hide_id) previous_toplevel.disconnect( self._toplevel_window_state_event_id) toplevel = self.get_toplevel() if isinstance(toplevel, gtk.Window): self._toplevel_hide_id = toplevel.connect( 'hide', self.on_toplevel_hide) self._toplevel_window_state_event_id = toplevel.connect( 'window-state-event', self.on_toplevel_window_state_event) self.popup.set_transient_for(toplevel) def do_scroll_event(self, event): """ Changes the current track """ if event.direction == gtk.gdk.SCROLL_UP: self.view.playlist.prev() elif event.direction == gtk.gdk.SCROLL_DOWN: self.view.playlist.next() else: return position = self.view.playlist.current_position try: track = self.view.playlist[position] except IndexError: pass else: player.QUEUE.play(track) def do_toggled(self): """ Shows or hides the playlist """ if self.get_active(): self.arrow.props.arrow_type = gtk.ARROW_DOWN self.popup.show_all() else: self.popup.hide() self.arrow.props.arrow_type = gtk.ARROW_RIGHT def on_accelerator_activate(self, accel_group, acceleratable, keyval, modifier): """ Shows the current track """ self.view.scroll_to_cell(self.view.playlist.current_position) self.view.set_cursor(self.view.playlist.current_position) def on_drag_motion(self, widget, context, x, y, time): """ Prepares to show the playlist """ # Defer display of the playlist if self._drag_motion_timeout_id is None: self._drag_motion_timeout_id = glib.timeout_add( 500, lambda: self.set_active(True)) # Prevent hiding of the playlist if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None def on_drag_leave(self, widget, context, time): """ Prepares to hide the playlist """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: glib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) # Defer hiding of the playlist self._drag_leave_timeout_id = glib.timeout_add( 500, lambda: self.set_active(False)) def on_drag_data_received(self, widget, context, x, y, selection, info, time): """ Handles dropped data """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: glib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None # Enable hiding of the playlist on re-enter if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None self.view.emit('drag-data-received', context, x, y, selection, info, time) def on_toplevel_hide(self, widget): """ Hides the playlist """ self.set_active(False) def on_toplevel_window_state_event(self, widget, event): """ Hides the playlist """ self.set_active(False) def on_popup_configure_event(self, widget, event): """ Saves the window size after resizing """ width = settings.get_option('plugin/minimode/' 'playlist_button_popup_width', 350) height = settings.get_option('plugin/minimode/' 'playlist_button_popup_height', 400) if event.width != width: settings.set_option('plugin/minimode/' 'playlist_button_popup_width', event.width) if event.height != height: settings.set_option('plugin/minimode/' 'playlist_button_popup_height', event.height) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ glib.idle_add(self.update_playlist, playlist) def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ try: track = playlist[positions[0]] except IndexError: text = '' else: text = self.formatter.format(track) glib.idle_add(self.label.set_text, text) def on_track_tags_changed(self, event, track, tag): """ Updates the button on tag changes """ playlist = self.view.playlist track_position = playlist.index(track) if track in playlist and track_position == playlist.current_position: glib.idle_add(self.label.set_text, self.formatter.format(track)) def on_option_set(self, event, settings, option): """ Updates control upon setting change """ if option == 'plugin/minimode/track_title_format': glib.idle_add(self.formatter.set_property, 'format', settings.get_option(option, _('$tracknumber - $title')) )
class BaseTrayIcon(object): """ Trayicon base, needs to be derived from """ def __init__(self, main): self.main = main self.VOLUME_STEP = 0.05 self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) self.tooltip.set_display_progress(True) self.menu = menu.ProviderMenu('tray-icon-context', self) self.update_icon() self.connect_events() event.log_event('tray_icon_toggled', self, True) def destroy(self): """ Unhides the window and removes the tray icon """ # FIXME: Allow other windows too if not self.main.window.get_property('visible'): self.main.window.deiconify() self.main.window.present() self.disconnect_events() self.set_visible(False) self.tooltip.destroy() event.log_event('tray_icon_toggled', self, False) def connect_events(self): """ Connects various callbacks with events """ self.connect('button-press-event', self.on_button_press_event) self.connect('scroll-event', self.on_scroll_event) event.add_ui_callback(self.on_playback_change_state, 'playback_player_end', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_track_start', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER) event.add_ui_callback(self.on_playback_change_state, 'playback_error', player.PLAYER) def disconnect_events(self): """ Disconnects various callbacks from events """ event.remove_callback(self.on_playback_change_state, 'playback_player_end', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_track_start', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_toggle_pause', player.PLAYER) event.remove_callback(self.on_playback_change_state, 'playback_error', player.PLAYER) def update_icon(self): """ Updates icon appearance based on current playback state """ if player.PLAYER.current is None: self.set_from_icon_name('exaile') self.set_tooltip(_('Exaile Music Player')) elif player.PLAYER.is_paused(): self.set_from_icon_name('exaile-pause') else: self.set_from_icon_name('exaile-play') def set_from_icon_name(self, icon_name): """ Updates the tray icon """ pass def set_tooltip(self, tooltip_text): """ Updates the tray icon tooltip """ pass def set_visible(self, visible): """ Shows or hides the tray icon """ pass def get_menu_position(self, menu, icon): """ Returns coordinates for the best menu position """ return (0, 0, False) def on_button_press_event(self, widget, event): """ Toggles main window visibility and pause as well as opens the context menu """ if event.button == 1: self.main.toggle_visible(bringtofront=True) if event.button == 2: playback.playpause( player.PLAYER ) if event.button == 3: self.menu.popup(None, None, self.get_menu_position, self, event.button, event.time) def on_scroll_event(self, widget, event): """ Changes volume and skips tracks on scroll """ if event.get_state() & Gdk.ModifierType.SHIFT_MASK: if event.direction == Gdk.ScrollDirection.UP: player.QUEUE.prev() elif event.direction == Gdk.ScrollDirection.DOWN: player.QUEUE.next() else: if event.direction == Gdk.ScrollDirection.UP: volume = settings.get_option('player/volume', 1) settings.set_option('player/volume', min(volume + self.VOLUME_STEP, 1)) return True elif event.direction == Gdk.ScrollDirection.DOWN: volume = settings.get_option('player/volume', 1) settings.set_option('player/volume', max(0, volume - self.VOLUME_STEP)) return True elif event.direction == Gdk.ScrollDirection.LEFT: player.QUEUE.prev() elif event.direction == Gdk.ScrollDirection.RIGHT: player.QUEUE.next() def on_playback_change_state(self, event, player, current): """ Updates tray icon appearance on playback state change """ self.update_icon()
class PlaylistButtonControl(gtk.ToggleButton, BaseControl, QueueAdapter): name = 'playlist_button' title = _('Playlist button') description = _('Access the current playlist') __gsignals__ = {'scroll-event': 'override'} def __init__(self): gtk.ToggleButton.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.set_focus_on_click(False) self.set_size_request(200, -1) box = gtk.HBox() self.arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_OUT) box.pack_start(self.arrow, expand=False) self.label = gtk.Label('') self.label.props.ellipsize = pango.ELLIPSIZE_END box.pack_start(self.label) self.add(box) self.formatter = TrackFormatter( settings.get_option('plugin/minimode/track_title_format', '$tracknumber - $title')) self.view = PlaylistView(player.QUEUE.current_playlist, player.PLAYER) self.popup = AttachedWindow(self) self.popup.set_default_size( settings.get_option( 'plugin/minimode/' 'playlist_button_popup_width', 350), settings.get_option( 'plugin/minimode/' 'playlist_button_popup_height', 400)) scrollwindow = gtk.ScrolledWindow() scrollwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrollwindow.set_shadow_type(gtk.SHADOW_IN) scrollwindow.add(self.view) self.popup.add(scrollwindow) self.popup.connect('configure-event', self.on_popup_configure_event) accel_group = gtk.AccelGroup() key, modifier = gtk.accelerator_parse('<Control>J') accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, self.on_accelerator_activate) self.popup.add_accel_group(accel_group) self.tooltip = TrackToolTip(self, player.PLAYER) self.tooltip.set_auto_update(True) if player.PLAYER.current is not None: self.label.set_text(self.formatter.format(player.PLAYER.current)) self._drag_motion_timeout_id = None self._drag_leave_timeout_id = None self._toplevel_hide_id = None self._toplevel_window_state_event_id = None self.drag_dest_set( gtk.DEST_DEFAULT_ALL, self.view.targets, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE) self.connect('drag-motion', self.on_drag_motion) self.connect('drag-leave', self.on_drag_leave) self.connect('drag-data-received', self.on_drag_data_received) self.view.connect('drag-motion', self.on_drag_motion) self.view.connect('drag-leave', self.on_drag_leave) event.add_callback(self.on_track_tags_changed, 'track_tags_changed') event.add_callback(self.on_option_set, 'plugin_minimode_option_set') self.on_option_set('plugin_minimode_option_set', settings, 'plugin/minimode/track_title_format') def destroy(self): """ Cleanups """ self.tooltip.destroy() QueueAdapter.destroy(self) gtk.ToggleButton.destroy(self) def update_playlist(self, playlist): """ Updates the internally stored playlist """ columns = self.view.get_model().columns model = PlaylistModel(playlist, columns, player.PLAYER) self.view.set_model(model) def do_hierarchy_changed(self, previous_toplevel): """ Sets up automatic hiding on parent hide """ if self._toplevel_hide_id is not None: previous_toplevel.disconnect(self._toplevel_hide_id) previous_toplevel.disconnect(self._toplevel_window_state_event_id) toplevel = self.get_toplevel() if isinstance(toplevel, gtk.Window): self._toplevel_hide_id = toplevel.connect('hide', self.on_toplevel_hide) self._toplevel_window_state_event_id = toplevel.connect( 'window-state-event', self.on_toplevel_window_state_event) self.popup.set_transient_for(toplevel) def do_scroll_event(self, event): """ Changes the current track """ if event.direction == gtk.gdk.SCROLL_UP: self.view.playlist.prev() elif event.direction == gtk.gdk.SCROLL_DOWN: self.view.playlist.next() else: return position = self.view.playlist.current_position try: track = self.view.playlist[position] except IndexError: pass else: player.QUEUE.play(track) def do_toggled(self): """ Shows or hides the playlist """ if self.get_active(): self.arrow.props.arrow_type = gtk.ARROW_DOWN self.popup.show_all() else: self.popup.hide() self.arrow.props.arrow_type = gtk.ARROW_RIGHT def on_accelerator_activate(self, accel_group, acceleratable, keyval, modifier): """ Shows the current track """ self.view.scroll_to_cell(self.view.playlist.current_position) self.view.set_cursor(self.view.playlist.current_position) def on_drag_motion(self, widget, context, x, y, time): """ Prepares to show the playlist """ # Defer display of the playlist if self._drag_motion_timeout_id is None: self._drag_motion_timeout_id = glib.timeout_add( 500, lambda: self.set_active(True)) # Prevent hiding of the playlist if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None def on_drag_leave(self, widget, context, time): """ Prepares to hide the playlist """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: glib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) # Defer hiding of the playlist self._drag_leave_timeout_id = glib.timeout_add( 500, lambda: self.set_active(False)) def on_drag_data_received(self, widget, context, x, y, selection, info, time): """ Handles dropped data """ # Enable display of the playlist on re-enter if self._drag_motion_timeout_id is not None: glib.source_remove(self._drag_motion_timeout_id) self._drag_motion_timeout_id = None # Enable hiding of the playlist on re-enter if self._drag_leave_timeout_id is not None: glib.source_remove(self._drag_leave_timeout_id) self._drag_leave_timeout_id = None self.view.emit('drag-data-received', context, x, y, selection, info, time) def on_toplevel_hide(self, widget): """ Hides the playlist """ self.set_active(False) def on_toplevel_window_state_event(self, widget, event): """ Hides the playlist """ self.set_active(False) def on_popup_configure_event(self, widget, event): """ Saves the window size after resizing """ width = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_width', 350) height = settings.get_option( 'plugin/minimode/' 'playlist_button_popup_height', 400) if event.width != width: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_width', event.width) if event.height != height: settings.set_option( 'plugin/minimode/' 'playlist_button_popup_height', event.height) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ glib.idle_add(self.update_playlist, playlist) def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ try: track = playlist[positions[0]] except IndexError: text = '' else: text = self.formatter.format(track) glib.idle_add(self.label.set_text, text) def on_track_tags_changed(self, event, track, tag): """ Updates the button on tag changes """ playlist = self.view.playlist track_position = playlist.index(track) if track in playlist and track_position == playlist.current_position: glib.idle_add(self.label.set_text, self.formatter.format(track)) def on_option_set(self, event, settings, option): """ Updates control upon setting change """ if option == 'plugin/minimode/track_title_format': glib.idle_add( self.formatter.set_property, 'format', settings.get_option(option, _('$tracknumber - $title')))