Exemple #1
0
    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')
Exemple #2
0
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)
Exemple #4
0
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}')
Exemple #5
0
    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')
Exemple #6
0
    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')
Exemple #7
0
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())
Exemple #8
0
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())
Exemple #9
0
    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'
        )
Exemple #10
0
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')))
Exemple #11
0
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')))
Exemple #12
0
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,
        )
Exemple #13
0
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)
Exemple #14
0
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'))
            )
Exemple #15
0
    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')
Exemple #16
0
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'))
            )
Exemple #17
0
    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'
        )
Exemple #18
0
    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')
Exemple #19
0
    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')
Exemple #20
0
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'))
            )
Exemple #21
0
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')))