Commit 03785a4e authored by Marinus Schraal's avatar Marinus Schraal

widgets: Split up the widgets

parent 0ccd89f6
......@@ -53,6 +53,7 @@ AC_CONFIG_FILES([
data/AboutDialog.ui
gnomemusic/Makefile
gnomemusic/views/Makefile
gnomemusic/widgets/Makefile
po/Makefile.in
libgd/Makefile
])
......
SUBDIRS = views
SUBDIRS = views widgets
appdir = $(pythondir)/gnomemusic/
......@@ -14,7 +14,6 @@ app_PYTHON = \
playlists.py\
utils.py \
query.py \
widgets.py \
searchbar.py \
window.py
......@@ -30,8 +30,8 @@ from gnomemusic.albumartcache import ArtSize
from gnomemusic.grilo import grilo
from gnomemusic.toolbar import ToolbarState
from gnomemusic.views.baseview import BaseView
from gnomemusic.widgets.albumwidget import AlbumWidget
import gnomemusic.utils as utils
import gnomemusic.widgets as Widgets
class AlbumsView(BaseView):
......@@ -42,7 +42,7 @@ class AlbumsView(BaseView):
@log
def __init__(self, window, player):
BaseView.__init__(self, 'albums', _("Albums"), window, None)
self._albumWidget = Widgets.AlbumWidget(player, self)
self._albumWidget = AlbumWidget(player, self)
self.player = player
self.add(self._albumWidget)
self.albums_selected = []
......
......@@ -28,8 +28,8 @@ from gi.repository import Gd, GLib, Gtk, Pango
from gnomemusic import log
from gnomemusic.grilo import grilo
from gnomemusic.views.baseview import BaseView
from gnomemusic.widgets.artistalbumswidget import ArtistAlbumsWidget
import gnomemusic.utils as utils
import gnomemusic.widgets as Widgets
class ArtistsView(BaseView):
......@@ -135,7 +135,7 @@ class ArtistsView(BaseView):
artistAlbums = None
artistAlbums = Widgets.ArtistAlbums(
artistAlbums = ArtistAlbumsWidget(
artist, albums, self.player,
self.header_bar, self.selection_toolbar, self.window
)
......
......@@ -28,7 +28,7 @@ from gi.repository import Gd, Gdk, GdkPixbuf, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
import gnomemusic.widgets as Widgets
from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
import gnomemusic.utils as utils
......@@ -86,7 +86,7 @@ class BaseView(Gtk.Stack):
if not use_sidebar or sidebar:
self._grid.add(self._box)
self.star_handler = Widgets.StarHandler(self, 9)
self.star_handler = StarHandlerWidget(self, 9)
self._cursor = None
self.window = window
self.header_bar = window.toolbar
......
......@@ -33,8 +33,9 @@ from gnomemusic.playlists import Playlists
from gnomemusic.query import Query
from gnomemusic.toolbar import ToolbarState
from gnomemusic.views.baseview import BaseView
from gnomemusic.widgets.albumwidget import AlbumWidget
from gnomemusic.widgets.artistalbumswidget import ArtistAlbumsWidget
import gnomemusic.utils as utils
import gnomemusic.widgets as Widgets
playlists = Playlists.get_default()
......@@ -81,7 +82,7 @@ class SearchView(BaseView):
self.albums_selected = []
self._albums = {}
self._albumWidget = Widgets.AlbumWidget(player, self)
self._albumWidget = AlbumWidget(player, self)
self.add(self._albumWidget)
self.artists_albums_selected = []
......@@ -138,7 +139,7 @@ class SearchView(BaseView):
artist = self.model.get_value(_iter, 2)
albums = self._artists[artist.casefold()]['albums']
self._artistAlbumsWidget = Widgets.ArtistAlbums(
self._artistAlbumsWidget = ArtistAlbumsWidget(
artist, albums, self.player,
self.header_bar, self.selection_toolbar, self.window, True
)
......
appdir = $(pythondir)/gnomemusic/widgets/
app_PYTHON = \
__init__.py \
albumwidget.py \
artistalbumswidget.py \
artistalbumwidget.py \
playlistdialog.py \
starhandlerwidget.py
# Copyright (c) 2016 The GNOME Music Developers
#
# GNOME Music is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# GNOME Music is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# The GNOME Music authors hereby grant permission for non-GPL compatible
# GStreamer plugins to be used and distributed together with GStreamer
# and GNOME Music. This permission is above and beyond the permissions
# granted by the GPL license by which GNOME Music is covered. If you
# modify this code, you may extend this exception to your version of the
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
import logging
from gettext import gettext as _, ngettext
from gi.repository import GLib, GObject, Gtk
from gnomemusic import log
from gnomemusic.widgets.artistalbumwidget import ArtistAlbumWidget
import gnomemusic.utils as utils
logger = logging.getLogger(__name__)
class ArtistAlbumsWidget(Gtk.Box):
def __repr__(self):
return '<ArtistAlbumsWidget>'
@log
def __init__(self, artist, albums, player,
header_bar, selection_toolbar, window, selectionModeAllowed=False):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
self.player = player
self.artist = artist
self.albums = albums
self.window = window
self.selectionMode = False
self.selectionModeAllowed = selectionModeAllowed
self.selection_toolbar = selection_toolbar
self.header_bar = header_bar
self.ui = Gtk.Builder()
self.ui.add_from_resource('/org/gnome/Music/ArtistAlbumsWidget.ui')
self.set_border_width(0)
self.ui.get_object('artist').set_label(self.artist)
self.widgets = []
self.model = Gtk.ListStore(GObject.TYPE_STRING, # title
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_BOOLEAN, # icon shown
GObject.TYPE_STRING, # icon
GObject.TYPE_OBJECT, # song object
GObject.TYPE_BOOLEAN,
GObject.TYPE_INT
)
self.row_changed_source_id = None
self._hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self._albumBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
spacing=48)
self._scrolledWindow = Gtk.ScrolledWindow()
self._scrolledWindow.set_policy(
Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC)
self._scrolledWindow.add(self._hbox)
self._hbox.pack_start(self.ui.get_object('ArtistAlbumsWidget'),
False, False, 0)
self._hbox.pack_start(self._albumBox, False, False, 16)
self._coverSizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
self._songsGridSizeGroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
self.pack_start(self._scrolledWindow, True, True, 0)
self.hide()
self.window._init_loading_notification()
for album in albums:
is_last_album = False
if album == albums[-1]:
is_last_album = True
self.add_album(album, is_last_album)
self.player.connect('playlist-item-changed', self.update_model)
def _on_last_album_displayed(self, data=None):
self.window.notification.dismiss()
self.show_all()
@log
def add_album(self, album, is_last_album=False):
self.window.notification.set_timeout(0)
widget = ArtistAlbumWidget(
self.artist, album, self.player, self.model,
self.header_bar, self.selectionModeAllowed
)
self._coverSizeGroup.add_widget(widget.cover)
self._songsGridSizeGroup.add_widget(widget.songsGrid)
self._albumBox.pack_start(widget, False, False, 0)
self.widgets.append(widget)
if is_last_album:
widget.connect('tracks-loaded', self._on_last_album_displayed)
@log
def update_model(self, player, playlist, currentIter):
# this is not our playlist, return
if playlist != self.model:
# TODO, only clean once, but that can wait util we have clean
# the code a bit, and until the playlist refactoring.
# the overhead is acceptable for now
self.clean_model()
return False
currentSong = playlist.get_value(currentIter, 5)
song_passed = False
itr = playlist.get_iter_first()
while itr:
song = playlist.get_value(itr, 5)
song_widget = song.song_widget
if not song_widget.can_be_played:
itr = playlist.iter_next(itr)
continue
escaped_title = GLib.markup_escape_text(utils.get_media_title(song))
if (song == currentSong):
song_widget.now_playing_sign.show()
song_widget.title.set_markup('<b>%s</b>' % escaped_title)
song_passed = True
elif (song_passed):
song_widget.now_playing_sign.hide()
song_widget.title.set_markup('<span>%s</span>' % escaped_title)
else:
song_widget.now_playing_sign.hide()
song_widget.title.set_markup(
'<span color=\'grey\'>%s</span>' % escaped_title
)
itr = playlist.iter_next(itr)
return False
@log
def clean_model(self):
itr = self.model.get_iter_first()
while itr:
song = self.model.get_value(itr, 5)
song_widget = song.song_widget
escaped_title = GLib.markup_escape_text(utils.get_media_title(song))
if song_widget.can_be_played:
song_widget.now_playing_sign.hide()
song_widget.title.set_markup('<span>%s</span>' % escaped_title)
itr = self.model.iter_next(itr)
return False
@log
def set_selection_mode(self, selectionMode):
if self.selectionMode == selectionMode:
return
self.selectionMode = selectionMode
try:
if self.row_changed_source_id:
self.model.disconnect(self.row_changed_source_id)
self.row_changed_source_id = self.model.connect('row-changed', self._model_row_changed)
except Exception as e:
logger.warning("Exception while tracking row-changed: %s", e)
for widget in self.widgets:
widget.set_selection_mode(selectionMode)
@log
def _model_row_changed(self, model, path, _iter):
if not self.selectionMode:
return
selected_items = 0
for row in model:
if row[6]:
selected_items += 1
self.selection_toolbar\
._add_to_playlist_button.set_sensitive(selected_items > 0)
if selected_items > 0:
self.header_bar._selection_menu_label.set_text(
ngettext("Selected %d item", "Selected %d items", selected_items) % selected_items)
else:
self.header_bar._selection_menu_label.set_text(_("Click on items to select them"))
# Copyright (c) 2016 The GNOME Music Developers
#
# GNOME Music is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# GNOME Music is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# The GNOME Music authors hereby grant permission for non-GPL compatible
# GStreamer plugins to be used and distributed together with GStreamer
# and GNOME Music. This permission is above and beyond the permissions
# granted by the GPL license by which GNOME Music is covered. If you
# modify this code, you may extend this exception to your version of the
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
import logging
from gettext import gettext as _
from gi.repository import Gdk, Gio, GLib, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
from gnomemusic.player import DiscoveryStatus
from gnomemusic.playlists import Playlists
import gnomemusic.utils as utils
logger = logging.getLogger(__name__)
NOW_PLAYING_ICON_NAME = 'media-playback-start-symbolic'
ERROR_ICON_NAME = 'dialog-error-symbolic'
try:
settings = Gio.Settings.new('org.gnome.Music')
MAX_TITLE_WIDTH = settings.get_int('max-width-chars')
except Exception as e:
MAX_TITLE_WIDTH = 20
logger.error("Error on setting widget max-width-chars: %s", str(e))
playlists = Playlists.get_default()
class ArtistAlbumWidget(Gtk.Box):
__gsignals__ = {
'tracks-loaded': (GObject.SignalFlags.RUN_FIRST, None, ()),
}
def __repr__(self):
return '<ArtistAlbumWidget>'
@log
def __init__(self, artist, album, player, model, header_bar, selectionModeAllowed):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading,
ArtSize.large)
self._no_artwork_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.music,
ArtSize.large)
self.player = player
self.album = album
self.artist = artist
self.model = model
self.model.connect('row-changed', self._model_row_changed)
self.header_bar = header_bar
self.selectionMode = False
self.selectionModeAllowed = selectionModeAllowed
self.songs = []
self.ui = Gtk.Builder()
self.ui.add_from_resource('/org/gnome/Music/ArtistAlbumWidget.ui')
GLib.idle_add(self._update_album_art)
self.cover = self.ui.get_object('cover')
self.cover.set_from_surface(self._loading_icon_surface)
self.songsGrid = self.ui.get_object('grid1')
self.ui.get_object('title').set_label(album.get_title())
if album.get_creation_date():
self.ui.get_object('year').set_markup(
'<span color=\'grey\'>(%s)</span>' %
str(album.get_creation_date().get_year())
)
self.tracks = []
grilo.populate_album_songs(album, self.add_item)
self.pack_start(self.ui.get_object('ArtistAlbumWidget'), True, True, 0)
@log
def add_item(self, source, prefs, track, remaining, data=None):
if remaining == 0:
self.songsGrid.show_all()
self.emit("tracks-loaded")
if track:
self.tracks.append(track)
else:
for i, track in enumerate(self.tracks):
ui = Gtk.Builder()
ui.add_from_resource('/org/gnome/Music/TrackWidget.ui')
song_widget = ui.get_object('eventbox1')
self.songs.append(song_widget)
ui.get_object('num')\
.set_markup('<span color=\'grey\'>%d</span>'
% len(self.songs))
title = utils.get_media_title(track)
ui.get_object('title').set_text(title)
ui.get_object('title').set_alignment(0.0, 0.5)
ui.get_object('title').set_max_width_chars(MAX_TITLE_WIDTH)
self.songsGrid.attach(
song_widget,
int(i / (len(self.tracks) / 2)),
int(i % (len(self.tracks) / 2)), 1, 1
)
track.song_widget = song_widget
itr = self.model.append(None)
song_widget._iter = itr
song_widget.model = self.model
song_widget.title = ui.get_object('title')
song_widget.checkButton = ui.get_object('select')
song_widget.checkButton.set_visible(self.selectionMode)
song_widget.checkButton.connect(
'toggled', self._check_button_toggled, song_widget
)
self.model.set(itr,
[0, 1, 2, 3, 5],
[title, '', '', False, track])
song_widget.now_playing_sign = ui.get_object('image1')
song_widget.now_playing_sign.set_from_icon_name(
NOW_PLAYING_ICON_NAME,
Gtk.IconSize.SMALL_TOOLBAR)
song_widget.now_playing_sign.set_no_show_all('True')
song_widget.now_playing_sign.set_alignment(1, 0.6)
song_widget.can_be_played = True
song_widget.connect('button-release-event',
self.track_selected)
@log
def _update_album_art(self):
self._cache.lookup(self.album, ArtSize.medium, self._get_album_cover,
None)
@log
def _get_album_cover(self, surface, data=None):
if not surface:
surface = self._no_artwork_icon_surface
self.cover.set_from_surface(surface)
@log
def track_selected(self, widget, event):
if not widget.can_be_played:
return
if not self.selectionMode and \
(event.button == Gdk.BUTTON_SECONDARY or
(event.button == 1 and event.state & Gdk.ModifierType.CONTROL_MASK)):
if self.selectionModeAllowed:
self.header_bar._select_button.set_active(True)
else:
return
if self.selectionMode:
self.model[widget._iter][6] = not self.model[widget._iter][6]
return
self.player.stop()
self.player.set_playlist('Artist', self.artist,
widget.model, widget._iter, 5, 6)
self.player.set_playing(True)
@log
def set_selection_mode(self, selectionMode):
if self.selectionMode == selectionMode:
return
self.selectionMode = selectionMode
for songWidget in self.songs:
songWidget.checkButton.set_visible(selectionMode)
if not selectionMode:
songWidget.model[songWidget._iter][6] = False
@log
def _check_button_toggled(self, button, songWidget):
if songWidget.model[songWidget._iter][6] != button.get_active():
songWidget.model[songWidget._iter][6] = button.get_active()
@log
def _model_row_changed(self, model, path, _iter):
if not self.selectionMode:
return
if not model[_iter][5]:
return
songWidget = model[_iter][5].song_widget
selected = model[_iter][6]
if model[_iter][11] == DiscoveryStatus.FAILED:
songWidget.now_playing_sign.set_from_icon_name(
ERROR_ICON_NAME,
Gtk.IconSize.SMALL_TOOLBAR)
songWidget.now_playing_sign.show()
songWidget.can_be_played = False
if selected != songWidget.checkButton.get_active():
songWidget.checkButton.set_active(selected)
# Copyright (c) 2016 The GNOME Music Developers
#
# GNOME Music is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# GNOME Music is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# The GNOME Music authors hereby grant permission for non-GPL compatible
# GStreamer plugins to be used and distributed together with GStreamer
# and GNOME Music. This permission is above and beyond the permissions
# granted by the GPL license by which GNOME Music is covered. If you
# modify this code, you may extend this exception to your version of the
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
from gi.repository import Gtk, Gd, GLib, Pango, Gio
from gnomemusic import log
from gnomemusic.grilo import grilo
from gnomemusic.playlists import Playlists
import gnomemusic.utils as utils
class PlaylistDialog():
def __repr__(self):
return '<PlaylistDialog>'
@log
def __init__(self, parent):
self.ui = Gtk.Builder()
self.ui.add_from_resource('/org/gnome/Music/PlaylistDialog.ui')
self.dialog_box = self.ui.get_object('dialog1')
self.dialog_box.set_transient_for(parent)
self.view = self.ui.get_object('treeview1')
self.view.set_activate_on_single_click(False)
self.selection = self.ui.get_object('treeview-selection1')
self.selection.connect('changed', self._on_selection_changed)
self._add_list_renderers()
self.view.connect('row-activated', self._on_item_activated)
self.model = self.ui.get_object('liststore1')
self.populate()
self.title_bar = self.ui.get_object('headerbar1')
self.dialog_box.set_titlebar(self.title_bar)
self._cancel_button = self.ui.get_object('cancel-button')
self._select_button = self.ui.get_object('select-button')
self._select_button.set_sensitive(False)
self._cancel_button.connect('clicked', self._on_cancel_button_clicked)
self._select_button.connect('clicked', self._on_selection)
self._new_playlist_button = self.ui.get_object('new-playlist-button')
self._new_playlist_button.connect('clicked', self._on_editing_done)
self._new_playlist_entry = self.ui.get_object('new-playlist-entry')
self._new_playlist_entry.connect('changed',
self._on_new_playlist_entry_changed)
self._new_playlist_entry.connect('activate',
self._on_editing_done)
self._new_playlist_entry.connect('focus-in-event',
self._on_new_playlist_entry_focused)
self.playlist = Playlists.get_default()
self.playlist.connect('playlist-created', self._on_playlist_created)
@log
def get_selected(self):
_iter = self.selection.get_selected()[1]
if not _iter or self.model[_iter][1]:
return None
return self.model[_iter][2]
@log
def _add_list_renderers(self):
cols = Gtk.TreeViewColumn()
type_renderer = Gd.StyledTextRenderer(
xpad=8,
ypad=8,
ellipsize=Pango.EllipsizeMode.END,
xalign=0.0
)
cols.pack_start(type_renderer, True)
cols.add_attribute(type_renderer, "text", 0)
cols.set_cell_data_func(type_renderer, self._on_list_text_render)
self.view.append_column(cols)
@log
def populate(self):
if grilo.tracker:
GLib.idle_add(grilo.populate_playlists, 0, self._add_item)
@log
def _add_item(self, source, param, item, remaining=0, data=None):
if item:
self._add_item_to_model(item)
@log
def _add_item_to_model(self, item):
"""Adds (non-static only) playlists to the model"""
# Don't show static playlists
if self.playlist.is_static_playlist(item):
return None
new_iter = self.model.append()
self.model.set(
new_iter,
[0, 1, 2],
[utils.get_media_title(item), False, item]
)
return new_iter
@log
def _on_list_text_render(self, col, cell, model, _iter, data):
editable = model.get_value(_iter, 1)
if editable:
cell.add_class("dim-label")
else:
cell.remove_class("dim-label")
@log
def _on_selection(self, select_button):
self.dialog_box.response(Gtk.ResponseType.ACCEPT)
@log
def _on_cancel_button_clicked(self, cancel_button):
self.dialog_box.response(Gtk.ResponseType.REJECT)
@log
def _on_item_activated(self, view, path, column):
self._new_playlist_entry.set_text("")
self._new_playlist_button.set_sensitive(False)
_iter = self.model.get_iter(path)
if self.model.get_value(_iter, 1):
self.view.set_cursor(path, column, True)
else:
self.dialog_box.response(Gtk.ResponseType.ACCEPT)
@log
def _on_selection_changed(self, selection):
model, _iter = self.selection.get_selected()
if _iter == None or self.model.get_value(_iter, 1):
self._select_button.set_sensitive(False)
else:
self._select_button.set_sensitive(True)
@log
def _on_editing_done(self, sender, data=None):
if self._new_playlist_entry.get_text() != '':
self.playlist.create_playlist(self._new_playlist_entry.get_text())
@log
def _on_playlist_created(self, playlists, item):