1# Copyright (c) 2016 The GNOME Music Developers
2#
3# GNOME Music is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# GNOME Music is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16#
17# The GNOME Music authors hereby grant permission for non-GPL compatible
18# GStreamer plugins to be used and distributed together with GStreamer
19# and GNOME Music.  This permission is above and beyond the permissions
20# granted by the GPL license by which GNOME Music is covered.  If you
21# modify this code, you may extend this exception to your version of the
22# code, but you are not obligated to do so.  If you do not wish to do so,
23# delete this exception statement from your version.
24
25from gettext import gettext as _
26from gi.repository import Gdk, GObject, Gtk, Pango
27
28from gnomemusic.coresong import CoreSong
29from gnomemusic.utils import SongStateIcon
30from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
31
32
33@Gtk.Template(resource_path="/org/gnome/Music/ui/SongsView.ui")
34class SongsView(Gtk.ScrolledWindow):
35    """Main view of all songs sorted artistwise
36
37    Consists all songs along with songname, star, length, artist
38    and the album name.
39    """
40
41    __gtype_name__ = "SongsView"
42
43    icon_name = GObject.Property(
44        type=str, default="emblem-music-symbolic",
45        flags=GObject.ParamFlags.READABLE)
46    title = GObject.Property(
47        type=str, default=_("Songs"), flags=GObject.ParamFlags.READABLE)
48
49    _duration_renderer = Gtk.Template.Child()
50    _now_playing_column = Gtk.Template.Child()
51    _now_playing_cell = Gtk.Template.Child()
52    _songs_ctrlr = Gtk.Template.Child()
53    _songs_view = Gtk.Template.Child()
54    _star_column = Gtk.Template.Child()
55
56    def __init__(self, application):
57        """Initialize
58
59        :param GtkApplication window: The application object
60        """
61        super().__init__()
62
63        self.props.name = "songs"
64
65        self._window = application.props.window
66        self._coremodel = application.props.coremodel
67
68        self._iter_to_clean = None
69        self._set_list_renderers()
70
71        self._playlist_model = self._coremodel.props.playlist_sort
72        self._songs_view.props.model = self._coremodel.props.songs_gtkliststore
73        self._model = self._songs_view.props.model
74
75        self._player = application.props.player
76        self._player.connect('song-changed', self._update_model)
77
78        self._selection_mode = False
79
80        self._window.bind_property(
81            "selection-mode", self, "selection-mode",
82            GObject.BindingFlags.BIDIRECTIONAL)
83
84    def _set_list_renderers(self):
85        self._now_playing_column.set_cell_data_func(
86            self._now_playing_cell, self._on_list_widget_icon_render, None)
87
88        self._star_handler = StarHandlerWidget(self, 6)
89        self._star_handler.add_star_renderers(self._star_column)
90
91        attrs = Pango.AttrList()
92        attrs.insert(Pango.AttrFontFeatures.new("tnum=1"))
93        self._duration_renderer.props.attributes = attrs
94
95    def _on_list_widget_icon_render(self, col, cell, model, itr, data):
96        current_song = self._player.props.current_song
97        if current_song is None:
98            return
99
100        coresong = model[itr][7]
101        if coresong.props.validation == CoreSong.Validation.FAILED:
102            cell.props.icon_name = SongStateIcon.ERROR.value
103            cell.props.visible = True
104        elif coresong.props.grlid == current_song.props.grlid:
105            cell.props.icon_name = SongStateIcon.PLAYING.value
106            cell.props.visible = True
107        else:
108            cell.props.visible = False
109
110    @GObject.Property(type=bool, default=False)
111    def selection_mode(self):
112        """selection mode getter
113
114        :returns: If selection mode is active
115        :rtype: bool
116        """
117        return self._selection_mode
118
119    @selection_mode.setter  # type: ignore
120    def selection_mode(self, value):
121        """selection-mode setter
122
123        :param bool value: Activate selection mode
124        """
125        if (value == self._selection_mode
126                or self.get_parent().get_visible_child() != self):
127            return
128
129        self._selection_mode = value
130        if self._selection_mode is False:
131            self.deselect_all()
132
133        cols = self._songs_view.get_columns()
134        cols[1].props.visible = self._selection_mode
135
136    @Gtk.Template.Callback()
137    def _on_item_activated(self, treeview, path, column):
138        """Action performed when clicking on a song
139
140        clicking on star column toggles favorite
141        clicking on an other columns launches player
142
143        :param Gtk.TreeView treeview: self._songs_view
144        :param Gtk.TreePath path: activated row index
145        :param Gtk.TreeViewColumn column: activated column
146        """
147        if self._star_handler.star_renderer_click:
148            self._star_handler.star_renderer_click = False
149            return
150
151        if self.props.selection_mode:
152            return
153
154        itr = self._model.get_iter(path)
155        coresong = self._model[itr][7]
156        self._coremodel.props.active_core_object = coresong
157
158        self._player.play(coresong)
159
160    @Gtk.Template.Callback()
161    def _on_view_clicked(self, gesture, n_press, x, y):
162        """Ctrl+click on self._songs_view triggers selection mode."""
163        _, state = Gtk.get_current_event_state()
164        modifiers = Gtk.accelerator_get_default_mod_mask()
165        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
166                and not self.props.selection_mode):
167            self.props.selection_mode = True
168
169        # FIXME: In selection mode, star clicks might still trigger
170        # activation.
171        if self.props.selection_mode:
172            path = self._songs_view.get_path_at_pos(x, y)
173            if path is None:
174                return
175
176            iter_ = self._model.get_iter(path[0])
177            new_fav_status = not self._model[iter_][1]
178            self._model[iter_][1] = new_fav_status
179            self._model[iter_][7].props.selected = new_fav_status
180
181    def _update_model(self, player):
182        """Updates model when the song changes
183
184        :param Player player: The main player object
185        """
186        # iter_to_clean is necessary because of a bug in GtkTreeView
187        # See https://gitlab.gnome.org/GNOME/gtk/issues/503
188        if self._iter_to_clean:
189            self._model[self._iter_to_clean][9] = False
190
191        index = self._player.props.position
192        current_coresong = self._playlist_model[index]
193        for idx, liststore in enumerate(self._model):
194            if liststore[7] == current_coresong:
195                break
196
197        iter_ = self._model.get_iter_from_string(str(idx))
198        path = self._model.get_path(iter_)
199        self._model[iter_][9] = True
200        self._songs_view.scroll_to_cell(path, None, True, 0.5, 0.5)
201
202        if self._model[iter_][0] != SongStateIcon.ERROR.value:
203            self._iter_to_clean = iter_.copy()
204
205        return False
206
207    @GObject.Property(
208        type=Gtk.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
209    def model(self):
210        """Get songs view model
211
212        :returns: songs view model
213        :rtype: Gtk.ListStore
214        """
215        return self._model
216
217    def _select(self, value):
218        with self._model.freeze_notify():
219            itr = self._model.iter_children(None)
220            while itr is not None:
221                self._model[itr][7].props.selected = value
222                self._model[itr][1] = value
223
224                itr = self._model.iter_next(itr)
225
226    def select_all(self):
227        self._select(True)
228
229    def deselect_all(self):
230        self._select(False)
231