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