def __init__(self): Formatter.__init__(self, self.get_option_value()) self.track_formatter = TrackFormatter('') self.progress_formatter = ProgressTextFormatter( self.props.format, player.PLAYER) event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set')
class CommentColumn(EditableColumn): name = 'comment' display = _('Comment') size = 200 autoexpand = True # Remove the newlines to fit into the vertical space of rows formatter = TrackFormatter('${comment:newlines=strip}') edit_formatter = TrackFormatter('$comment') cellproperties = {'editable': True}
def on_editing_started(self, cellrenderer, editable, path): # Retrieve comment in original form model = self.get_tree_view().get_model() iter = model.get_iter(path) track = model.get_value(iter, 0) comment = TrackFormatter('$comment').format(track) # Escape newlines comment = comment.encode('unicode-escape') # Set text editable.set_text(comment)
class CommentColumn(Column): name = 'comment' display = _('Comment') size = 200 autoexpand = True # Remove the newlines to fit into the vertical space of rows formatter = TrackFormatter('${comment:newlines=strip}')
def __init__(self): Formatter.__init__(self, self.get_option_value()) self.track_formatter = TrackFormatter('') self.progress_formatter = ProgressTextFormatter( self.props.format, player.PLAYER ) event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set')
def __init__(self): Gtk.ComboBox.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.formatter = TrackFormatter('') self.model = Gtk.ListStore(object) self.set_model(self.model) self.synchronize() renderer = Gtk.CellRendererText() self.pack_start(renderer, True) self.set_cell_data_func(renderer, self.data_func) self.set_size_request(200, 0) 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')
class ProgressButtonFormatter(Formatter): """ Formatter which allows both for display of tag data as well as progress information """ def __init__(self): Formatter.__init__(self, self.get_option_value()) self.track_formatter = TrackFormatter('') self.progress_formatter = ProgressTextFormatter( self.props.format, player.PLAYER ) event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set') def format(self, current_time=None, total_time=None): """ Returns a string suitable for progress buttons :param current_time: the current progress :type current_time: float :param total_time: the total length of a track :type total_time: float :returns: The formatted text :rtype: string """ text = self.progress_formatter.format() self.track_formatter.props.format = text text = self.track_formatter.format(player.PLAYER.current) return text def get_option_value(self): """ Retrieves the current user format """ return settings.get_option( 'plugin/minimode/progress_button_title_format', _('$title ($current_time / $total_time)'), ) def on_option_set(self, event, settings, option): """ Updates the internal format on setting change """ if option == 'gui/progress_bar_text_format': GLib.idle_add(self.set_property, 'format', self.get_option_value())
class ProgressButtonFormatter(Formatter): """ Formatter which allows both for display of tag data as well as progress information """ def __init__(self): Formatter.__init__(self, self.get_option_value()) self.track_formatter = TrackFormatter('') self.progress_formatter = ProgressTextFormatter( self.props.format, player.PLAYER) event.add_ui_callback(self.on_option_set, 'plugin_minimode_option_set') def format(self, current_time=None, total_time=None): """ Returns a string suitable for progress buttons :param current_time: the current progress :type current_time: float :param total_time: the total length of a track :type total_time: float :returns: The formatted text :rtype: string """ text = self.progress_formatter.format() self.track_formatter.props.format = text text = self.track_formatter.format(player.PLAYER.current) return text def get_option_value(self): """ Retrieves the current user format """ return settings.get_option( 'plugin/minimode/progress_button_title_format', _('$title ($current_time / $total_time)'), ) def on_option_set(self, event, settings, option): """ Updates the internal format on setting change """ if option == 'gui/progress_bar_text_format': GLib.idle_add(self.set_property, 'format', self.get_option_value())
def __init__(self): Gtk.ComboBox.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.formatter = TrackFormatter('') self.model = Gtk.ListStore(object) self.set_model(self.model) self.synchronize() renderer = Gtk.CellRendererText() self.pack_start(renderer, True) self.set_cell_data_func(renderer, self.data_func) self.set_size_request(200, 0) 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' )
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 TrackSelectorControl(Gtk.ComboBox, BaseControl, QueueAdapter): name = 'track_selector' title = _('Track selector') description = _('Simple track list selector') __gsignals__ = {'changed': 'override'} def __init__(self): Gtk.ComboBox.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.formatter = TrackFormatter('') self.model = Gtk.ListStore(object) self.set_model(self.model) self.synchronize() renderer = Gtk.CellRendererText() self.pack_start(renderer, True) self.set_cell_data_func(renderer, self.data_func) self.set_size_request(200, 0) 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 """ QueueAdapter.destroy(self) Gtk.ComboBox.destroy(self) def data_func(self, column, cell, model, iter, user_data): """ Updates track titles and highlights the current track if the popup is shown """ track = model.get_value(iter, 0) if track is None: return cell.props.text = self.formatter.format(track) active_iter = self.get_active_iter() if active_iter is not None: active_track = model.get_value(active_iter, 0) weight = Pango.Weight.NORMAL if self.props.popup_shown and track == active_track: weight = Pango.Weight.BOLD cell.props.weight = weight @suppress('changed') def synchronize(self): """ Synchronizes the model data with the current content of the queue """ self.set_model(None) self.model.clear() for i, track in enumerate(player.QUEUE.current_playlist): iter = self.model.append([track]) if track is player.QUEUE.current_playlist.current: # Not using iter since model is detached self.set_active(i) self.set_model(self.model) def do_changed(self): """ Starts playing the selected track. Should only be triggered by user action to prevent race conditions """ active_index = self.get_active() if active_index > -1: player.QUEUE.current_playlist.current_position = active_index player.QUEUE.play(player.QUEUE.current_playlist[active_index]) def add_tracks(self, tracks): """ Adds tracks to the internal storage """ if not tracks: return self.set_model(None) for position, track in tracks: self.model.insert(position, [track]) self.set_model(self.model) def remove_tracks(self, tracks): """ Removes tracks from the internal storage """ if not tracks: return self.set_model(None) tracks.reverse() for position, track in tracks: del self.model[position] self.set_model(self.model) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ self.synchronize() @suppress('changed') def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ if positions[0] < 0: return GLib.idle_add(self.set_active, positions[0]) def on_queue_tracks_added(self, event, queue, tracks): """ Updates the list on queue changes """ GLib.idle_add(self.add_tracks, tracks) def on_queue_tracks_removed(self, event, queue, tracks): """ Updates the list on queue changes """ GLib.idle_add(self.remove_tracks, tracks) 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 Column(Gtk.TreeViewColumn): name = '' display = '' menu_title = classproperty(lambda c: c.display) renderer = Gtk.CellRendererText formatter = classproperty(lambda c: TrackFormatter('$%s' % c.name)) size = 10 # default size autoexpand = False # whether to expand to fit space in Autosize mode datatype = str dataproperty = 'text' cellproperties = {} def __init__(self, container, player, font, size_ratio): if self.__class__ == Column: raise NotImplementedError( "Can't instantiate " "abstract class %s" % repr(self.__class__) ) self._size_ratio = size_ratio self.container = container self.player = player self.settings_width_name = "gui/col_width_%s" % self.name self.cellrenderer = self.renderer() self.destroyed = False super(Column, self).__init__(self.display) self.props.min_width = 3 self.pack_start(self.cellrenderer, True) self.set_cell_data_func(self.cellrenderer, self.data_func) try: self.cellrenderer.set_property('font-desc', font) except TypeError: pass # not all cells have a font try: self.cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END) except TypeError: # cellrenderer doesn't do ellipsize - eg. rating pass for name, val in self.cellproperties.iteritems(): self.cellrenderer.set_property(name, val) self.set_reorderable(True) self.set_clickable(True) self.set_sizing(Gtk.TreeViewColumnSizing.FIXED) # needed for fixed-height mode self.set_sort_order(Gtk.SortType.DESCENDING) # hack to allow button press events on the header to be detected self.set_widget(Gtk.Label(label=self.display)) # Save the width of the column when it changes; save the notify id so # we don't emit an event when we're programmatically setting the width self._width_notify = self.connect('notify::width', self.on_width_changed) self._setup_sizing() event.add_ui_callback( self.on_option_set, "gui_option_set", destroy_with=container ) def on_option_set(self, typ, obj, data): if data in ("gui/resizable_cols", self.settings_width_name): self._setup_sizing() @common.glib_wait(100) def on_width_changed(self, column, wid): width = self.get_width() if not self.destroyed and width != settings.get_option( self.settings_width_name, -1 ): settings.set_option(self.settings_width_name, width) def _setup_sizing(self): with self.handler_block(self._width_notify): if settings.get_option('gui/resizable_cols', False): self.set_resizable(True) self.set_expand(False) width = settings.get_option(self.settings_width_name, self.size) self.set_fixed_width(width) else: self.set_resizable(False) if self.autoexpand: self.set_expand(True) self.set_fixed_width(1) else: self.set_expand(False) self.set_fixed_width(self.size) def set_size_ratio(self, ratio): self._size_ratio = ratio def get_size_ratio(self): '''Returns how much bigger or smaller an icon should be''' return self._size_ratio def data_func(self, col, cell, model, iter, user_data): # warning: this function gets called from the render function, so do as # little work as possible! cache = model.get_value(iter, 1) text = cache.get(self.name) if text is None: track = model.get_value(iter, 0) text = self.formatter.format(track) cache[self.name] = text cell.props.text = text def __repr__(self): return '%s(%r, %r, %r)' % ( self.__class__.__name__, self.name, self.display, self.size, )
class Column(Gtk.TreeViewColumn): name = '' display = '' menu_title = classproperty(lambda c: c.display) renderer = Gtk.CellRendererText formatter = classproperty(lambda c: TrackFormatter('$%s' % c.name)) size = 10 # default size autoexpand = False # whether to expand to fit space in Autosize mode datatype = str dataproperty = 'text' cellproperties = {} def __init__(self, container, index, player, font): if self.__class__ == Column: raise NotImplementedError("Can't instantiate " "abstract class %s" % repr(self.__class__)) self.container = container self.player = player self.settings_width_name = "gui/col_width_%s" % self.name self.cellrenderer = self.renderer() self.extrasize = 0 self._setup_font(font) if index == 2: super(Column, self).__init__(self.display) self.icon_cellr = Gtk.CellRendererPixbuf() pbufsize = self.get_icon_height() self.icon_cellr.set_fixed_size(pbufsize, pbufsize) self.extrasize = pbufsize self.icon_cellr.set_property('xalign', 0.0) self.pack_start(self.icon_cellr, False) self.pack_start(self.cellrenderer, True) self.set_attributes(self.icon_cellr, pixbuf=1) self.set_attributes(self.cellrenderer, **{self.dataproperty: index}) else: super(Column, self).__init__(self.display, self.cellrenderer, **{self.dataproperty: index}) self.set_cell_data_func(self.cellrenderer, self.data_func) try: self.cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END) except TypeError: #cellrenderer doesn't do ellipsize - eg. rating pass for name, val in self.cellproperties.iteritems(): self.cellrenderer.set_property(name, val) self.set_reorderable(True) self.set_clickable(True) self.set_sizing( Gtk.TreeViewColumnSizing.FIXED) # needed for fixed-height mode self.set_sort_order(Gtk.SortType.DESCENDING) # hack to allow button press events on the header to be detected self.set_widget(Gtk.Label(label=self.display)) self.connect('notify::width', self.on_width_changed) self._setup_sizing() event.add_ui_callback(self.on_option_set, "gui_option_set") def on_option_set(self, typ, obj, data): if data in ("gui/resizable_cols", self.settings_width_name): self._setup_sizing() def on_width_changed(self, column, wid): if not self.container.button_pressed: return width = self.get_width() if width != settings.get_option(self.settings_width_name, -1): settings.set_option(self.settings_width_name, width) def _setup_font(self, font): ''' This should be set even for non-text columns. ::param font:: is None or a Pango.FontDescription ''' default_font = Gtk.Widget.get_default_style().font_desc if font is None: font = default_font def_font_sz = float(default_font.get_size()) try: self.cellrenderer.set_property('font-desc', font) except TypeError: pass # how much has the font deviated from normal? self._font_ratio = font.get_size() / def_font_sz try: # adjust the display size of the column ratio = self._font_ratio # small fonts can be problematic.. # -> TODO: perhaps default widths could be specified # in character widths instead? then we could # calculate it instead of using arbitrary widths if ratio < 1: ratio = ratio * 1.25 self.size = max(int(self.size * ratio), 1) except AttributeError: pass def _setup_sizing(self): if settings.get_option('gui/resizable_cols', False): self.set_resizable(True) self.set_expand(False) width = settings.get_option(self.settings_width_name, self.size + self.extrasize) self.set_fixed_width(width) else: self.set_resizable(False) if self.autoexpand: self.set_expand(True) self.set_fixed_width(1) else: self.set_expand(False) self.set_fixed_width(self.size + self.extrasize) def get_icon_height(self): '''Returns a default icon height based on the font size''' sz = Gtk.icon_size_lookup(Gtk.IconSize.BUTTON)[1] return max(int(sz * self._font_ratio), 1) def get_icon_size_ratio(self): '''Returns how much bigger or smaller an icon should be''' return self._font_ratio def data_func(self, col, cell, model, iter, user_data): if type(cell) == Gtk.CellRendererText: playlist = self.container.playlist if playlist is not self.player.queue.current_playlist: return path = model.get_path(iter) track = model.get_value(iter, 0) if track == self.player.current and \ path[0] == playlist.get_current_position(): weight = Pango.Weight.HEAVY else: weight = Pango.Weight.NORMAL cell.props.weight = weight if -1 < playlist.spat_position < path[0] and \ playlist.shuffle_mode == 'disabled': cell.props.sensitive = False else: cell.props.sensitive = True def __repr__(self): return '%s(%r, %r, %r)' % (self.__class__.__name__, self.name, self.display, self.size)
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')) )
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')
class TrackSelectorControl(Gtk.ComboBox, BaseControl, QueueAdapter): name = 'track_selector' title = _('Track selector') description = _('Simple track list selector') __gsignals__ = {'changed': 'override'} def __init__(self): Gtk.ComboBox.__init__(self) BaseControl.__init__(self) QueueAdapter.__init__(self, player.QUEUE) self.formatter = TrackFormatter('') self.model = Gtk.ListStore(object) self.set_model(self.model) self.synchronize() renderer = Gtk.CellRendererText() self.pack_start(renderer, True) self.set_cell_data_func(renderer, self.data_func) self.set_size_request(200, 0) 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 """ QueueAdapter.destroy(self) Gtk.ComboBox.destroy(self) def data_func(self, column, cell, model, iter, user_data): """ Updates track titles and highlights the current track if the popup is shown """ track = model.get_value(iter, 0) if track is None: return cell.props.text = self.formatter.format(track) active_iter = self.get_active_iter() if active_iter is not None: active_track = model.get_value(active_iter, 0) weight = Pango.Weight.NORMAL if self.props.popup_shown and track == active_track: weight = Pango.Weight.BOLD cell.props.weight = weight @suppress('changed') def synchronize(self): """ Synchronizes the model data with the current content of the queue """ self.set_model(None) self.model.clear() for i, track in enumerate(player.QUEUE.current_playlist): self.model.append([track]) if track is player.QUEUE.current_playlist.current: # Not using iter since model is detached self.set_active(i) self.set_model(self.model) def do_changed(self): """ Starts playing the selected track. Should only be triggered by user action to prevent race conditions """ active_index = self.get_active() if active_index > -1: player.QUEUE.current_playlist.current_position = active_index player.QUEUE.play(player.QUEUE.current_playlist[active_index]) def add_tracks(self, tracks): """ Adds tracks to the internal storage """ if not tracks: return self.set_model(None) for position, track in tracks: self.model.insert(position, [track]) self.set_model(self.model) def remove_tracks(self, tracks): """ Removes tracks from the internal storage """ if not tracks: return self.set_model(None) tracks.reverse() for position, track in tracks: del self.model[position] self.set_model(self.model) def on_queue_current_playlist_changed(self, event, queue, playlist): """ Updates the list on queue changes """ self.synchronize() @suppress('changed') def on_queue_current_position_changed(self, event, playlist, positions): """ Updates the list on queue changes """ if positions[0] < 0: return GLib.idle_add(self.set_active, positions[0]) def on_queue_tracks_added(self, event, queue, tracks): """ Updates the list on queue changes """ GLib.idle_add(self.add_tracks, tracks) def on_queue_tracks_removed(self, event, queue, tracks): """ Updates the list on queue changes """ GLib.idle_add(self.remove_tracks, tracks) 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')) )
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 __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 __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')
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 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')))