1# Copyright 2006 Joe Wreschnig
2#        2016-17 Nick Boultbee
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8
9from quodlibet import _, print_d
10
11
12class Order(object):
13    """Base class for all play orders
14
15    In all methods:
16        `playlist` is a GTK+ `ListStore` containing at least an `AudioFile`
17                   as the first element in each row
18                   (in the future there may be more elements per row).
19
20        `iter`     is a `GtkTreeIter` for the song that just finished, if any.
21                   If the song is not in the list, this iter will be `None`.
22    """
23
24    name = "unknown_order"
25    """The name by which this order is known"""
26
27    display_name = _("Unknown")
28    """The (translated) display name"""
29
30    accelerated_name = _("_Unknown")
31    """The (translated) display name with (optional) accelerators"""
32
33    replaygain_profiles = ["track"]
34    """The ReplayGain mode(s) to use with this order.
35    Shuffled ones typically prefer track modes"""
36
37    priority = 100
38    """The priority relative to other orders of its type.
39    Larger numbers typically appear lower in lists."""
40
41    def __init__(self):
42        """Must have a zero-arg constructor"""
43        pass
44
45    def next(self, playlist, iter):
46        """Not called directly, but the default implementation of
47        `next_explicit` and `next_implicit` both just call this. """
48        raise NotImplementedError
49
50    def previous(self, playlist, iter):
51        """Not called directly, but the default implementation of
52        `previous_explicit` calls this.
53        Right now there is no such thing as `previous_implicit`."""
54        raise NotImplementedError
55
56    def set(self, playlist, iter):
57        """Not called directly, but the default implementations of
58        `set_explicit` and `set_implicit` call this. """
59        return iter
60
61    def next_explicit(self, playlist, iter):
62        """Not called directly, but the default implementations of
63       `set_explicit` and `set_implicit` call this."""
64        return self.next(playlist, iter)
65
66    def next_implicit(self, playlist, iter):
67        """Called when a song ends passively, e.g. it plays through."""
68        return self.next(playlist, iter)
69
70    def previous_explicit(self, playlist, iter):
71        """Called when the user presses a "Previous" button."""
72        return self.previous(playlist, iter)
73
74    def set_explicit(self, playlist, iter):
75        """Called when the user manually selects a song (at `iter`).
76        If desired the play order can override that, or just
77        log it and return the iter again.
78        Note that playlist.current_iter is the current iter, if any.
79
80        If the play order returns `None`,
81        no action will be taken by the player.
82        """
83        return self.set(playlist, iter)
84
85    def set_implicit(self, playlist, iter):
86        """Called when the song is set by a means other than the UI."""
87        return self.set(playlist, iter)
88
89    def reset(self, playlist):
90        """Called when there is no song ready to prepare for a new order.
91        Implementations should reset the state of the current order,
92        e.g. forgetting history / clearing pre-cached orders."""
93        pass
94
95    def __str__(self):
96        """By default there is no interesting state"""
97        return "<%s>" % self.display_name
98
99
100class OrderRemembered(Order):
101    """Shared class for all the shuffle modes that keep a memory
102    of their previously played songs."""
103
104    def __init__(self):
105        super(OrderRemembered, self).__init__()
106        self._played = []
107
108    def next(self, playlist, iter):
109        if iter is not None:
110            self._played.append(playlist.get_path(iter).get_indices()[0])
111
112    def previous(self, playlist, iter):
113        try:
114            path = self._played.pop()
115        except IndexError:
116            return None
117        else:
118            return playlist.get_iter(path)
119
120    def set(self, playlist, iter):
121        if iter is not None:
122            self._played.append(playlist.get_path(iter).get_indices()[0])
123        return iter
124
125    def reset(self, playlist):
126        del(self._played[:])
127
128    def remaining(self, playlist):
129        """Gets a map of all song indices to their song from the `playlist`
130        that haven't yet been played"""
131        all_indices = set(range(len(playlist)))
132        played = set(self._played)
133        print_d("Played %d of %d song(s)" % (len(self._played), len(playlist)))
134        remaining = list(all_indices.difference(played))
135        all_songs = playlist.get()
136        return {i: all_songs[i] for i in remaining}
137
138
139class OrderInOrder(Order):
140    """Keep to the order of the supplied playlist"""
141    name = "in_order"
142    display_name = _("In Order")
143    accelerated_name = _("_In Order")
144    replaygain_profiles = ["album", "track"]
145    priority = 0
146
147    def next(self, playlist, iter):
148        if iter is None:
149            return playlist.get_iter_first()
150        else:
151            return playlist.iter_next(iter)
152
153    def previous(self, playlist, iter):
154        if len(playlist) == 0:
155            return None
156        elif iter is None:
157            return playlist[(len(playlist) - 1,)].iter
158        else:
159            path = max(1, playlist.get_path(iter).get_indices()[0])
160            try:
161                return playlist.get_iter((path - 1,))
162            except ValueError:
163                return None
164