[gnome-music/wip/jfelder/album-cover-editordialog: 8/8] albumeditordialog: Initial cover support
- From: Jean Felder <jfelder src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/jfelder/album-cover-editordialog: 8/8] albumeditordialog: Initial cover support
- Date: Tue, 29 Jan 2019 22:40:21 +0000 (UTC)
commit b6f12da72f7232d8612652a0116719eef360e438
Author: Jean Felder <jfelder src gnome org>
Date: Thu Nov 8 19:02:52 2018 +0100
albumeditordialog: Initial cover support
AlbumEditorDialog adds the possibility to change the cover associated
with an album by selecting a new file.
This dialog can only be launched from the AlbumsView when
selection-mode is active.
Album name, artist, genre and year fields are also visible but they
are read only.
data/org.gnome.Music.gresource.xml | 1 +
data/ui/AlbumEditorDialog.ui | 468 ++++++++++++++++++++++++++++++++
data/ui/SelectionToolbar.ui | 12 +
gnomemusic/albumartcache.py | 6 +-
gnomemusic/views/albumsview.py | 46 +++-
gnomemusic/widgets/albumcover.py | 7 +-
gnomemusic/widgets/albumeditordialog.py | 113 ++++++++
gnomemusic/widgets/coverstack.py | 25 +-
gnomemusic/widgets/selectiontoolbar.py | 23 +-
gnomemusic/window.py | 29 +-
po/POTFILES.in | 2 +
11 files changed, 708 insertions(+), 24 deletions(-)
---
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index 200159ae..d3f50d66 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -6,6 +6,7 @@
<file>icons/initial-state.png</file>
<file preprocess="xml-stripblanks">ui/AboutDialog.ui</file>
<file preprocess="xml-stripblanks">ui/AlbumCover.ui</file>
+ <file preprocess="xml-stripblanks">ui/AlbumEditorDialog.ui</file>
<file preprocess="xml-stripblanks">ui/AlbumWidget.ui</file>
<file preprocess="xml-stripblanks">ui/AppMenu.ui</file>
<file preprocess="xml-stripblanks">ui/ArtistAlbumWidget.ui</file>
diff --git a/data/ui/AlbumEditorDialog.ui b/data/ui/AlbumEditorDialog.ui
new file mode 100644
index 00000000..ab989391
--- /dev/null
+++ b/data/ui/AlbumEditorDialog.ui
@@ -0,0 +1,468 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.16"/>
+ <template class="AlbumEditorDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog_vbox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="media_details">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_left">12</property>
+ <property name="margin_right">12</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">18</property>
+ <property name="spacing">24</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButton" id="_coverart_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="_choose_cover" swapped="no"/>
+ <child>
+ <object class="CoverStack" id="_cover_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ </object>
+ </child>
+ <style>
+ <class name="album-cover"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="album">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">36</property>
+ <property name="label" translatable="yes">Album</property>
+ <property name="xalign">1</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_album_suggestion">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">2</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="_album_entry">
+ <property name="sensitive">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="artist">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">36</property>
+ <property name="label" translatable="yes">Artist</property>
+ <property name="xalign">1</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_artist_suggestion">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">2</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="_artist_entry">
+ <property name="sensitive">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="genre">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">36</property>
+ <property name="label" translatable="yes">Genre</property>
+ <property name="xalign">1</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_genre_suggestion">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">2</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="_genre_entry">
+ <property name="sensitive">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="year">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Year</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="_year_suggestion">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">2</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="_year_entry">
+ <property name="sensitive">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">4</property>
+ <property name="width_chars">10</property>
+ <property name="max_width_chars">4</property>
+ <property name="input_purpose">number</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="index">-1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkHeaderBar" id="_title_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Edit Cover</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="_select_button">
+ <property name="label" translatable="yes">_Save</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="_on_selection" swapped="no"/>
+ <style>
+ <class name="suggested-action"/>
+ <class name="text-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/data/ui/SelectionToolbar.ui b/data/ui/SelectionToolbar.ui
index ec64876c..4a6e17a5 100644
--- a/data/ui/SelectionToolbar.ui
+++ b/data/ui/SelectionToolbar.ui
@@ -17,5 +17,17 @@
</style>
</object>
</child>
+ <child>
+ <object class="GtkButton" id="_edit_cover_button">
+ <property name="label" translatable="yes">Edit Cover</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="_on_edit_cover_button_clicked" swapped="no"/>
+ <style>
+ <class name="text-button"/>
+ </style>
+ </object>
+ </child>
</template>
</interface>
diff --git a/gnomemusic/albumartcache.py b/gnomemusic/albumartcache.py
index be35262b..323d2669 100644
--- a/gnomemusic/albumartcache.py
+++ b/gnomemusic/albumartcache.py
@@ -43,7 +43,7 @@ logger = logging.getLogger(__name__)
@log
-def _make_icon_frame(icon_surface, art_size=None, scale=1, default_icon=False):
+def make_icon_frame(icon_surface, art_size=None, scale=1, default_icon=False):
border = 3
degrees = pi / 180
radius = 3
@@ -122,7 +122,7 @@ class DefaultIcon(GObject.GObject):
icon_type.value, art_size.width / 3, scale, 0)
icon = icon_info.load_surface()
- icon_surface = _make_icon_frame(icon, art_size, scale, True)
+ icon_surface = make_icon_frame(icon, art_size, scale, True)
return icon_surface
@@ -322,7 +322,7 @@ class Art(GObject.GObject):
def _cache_hit(self, klass, pixbuf):
surface = Gdk.cairo_surface_create_from_pixbuf(
pixbuf, self._scale, None)
- surface = _make_icon_frame(surface, self._size, self._scale)
+ surface = make_icon_frame(surface, self._size, self._scale)
self._surface = surface
self._set_grilo_thumbnail_path()
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py
index 7712206b..52c17adb 100644
--- a/gnomemusic/views/albumsview.py
+++ b/gnomemusic/views/albumsview.py
@@ -22,10 +22,13 @@
# 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 GObject, Gtk
+from gi.repository import GLib, GObject, Grl, Gtk
from gnomemusic import log
+from gnomemusic.albumartcache import ArtLoader
from gnomemusic.grilo import grilo
from gnomemusic.views.baseview import BaseView
from gnomemusic.widgets.headerbar import HeaderBar
@@ -33,6 +36,8 @@ from gnomemusic.widgets.albumcover import AlbumCover
from gnomemusic.widgets.albumwidget import AlbumWidget
import gnomemusic.utils as utils
+logger = logging.getLogger(__name__)
+
class AlbumsView(BaseView):
@@ -191,3 +196,42 @@ class AlbumsView(BaseView):
def unselect_all(self):
self.children_selected = []
self._toggle_all_selection(False)
+
+ @log
+ def update_cover_from_selection(self, new_cover):
+ """Update cover of the selected album
+
+ :param str new_cover: cover path
+ """
+ if (not new_cover
+ or len(self.children_selected) != 1):
+ return
+
+ child = self.children_selected[0]
+ child_index = child.get_index()
+ album = child.props.media
+ tmp_media = Grl.Media.audio_new()
+ tmp_media.set_album(utils.get_album_title(album))
+ tmp_media.set_artist(utils.get_artist_name(album))
+ tmp_media.set_thumbnail(GLib.filename_to_uri(new_cover, None))
+
+ art_loader = ArtLoader()
+ art_loader.connect(
+ 'failed', self._on_loading_new_cover_failed, new_cover)
+ art_loader.connect(
+ 'succeeded', self._on_loading_new_cover_succeeded,
+ (new_cover, child_index))
+ art_loader.load(tmp_media)
+
+ @log
+ def _on_loading_new_cover_succeeded(self, klass, data):
+ new_cover, child_index = data
+ child = self._view.get_child_at_index(child_index)
+ media = child.props.media
+ media.set_thumbnail(GLib.filename_to_uri(new_cover, None))
+ child.props.media = media
+
+ @log
+ def _on_loading_new_cover_failed(self, klass, cover):
+ logger.warning(
+ "Unable to load new cover from file {}".format(cover))
diff --git a/gnomemusic/widgets/albumcover.py b/gnomemusic/widgets/albumcover.py
index f76ecb65..1f13a48d 100644
--- a/gnomemusic/widgets/albumcover.py
+++ b/gnomemusic/widgets/albumcover.py
@@ -105,7 +105,7 @@ class AlbumCover(Gtk.FlowBoxChild):
50 * self._nr_albums, self._cover_stack.update, media,
priority=GLib.PRIORITY_LOW)
- @GObject.Property(type=Grl.Media, flags=GObject.ParamFlags.READABLE)
+ @GObject.Property(type=Grl.Media)
def media(self):
"""Media item used in AlbumCover
@@ -114,6 +114,11 @@ class AlbumCover(Gtk.FlowBoxChild):
"""
return self._media
+ @media.setter
+ def media(self, media):
+ self._media = media
+ self._cover_stack.update(self._media)
+
@Gtk.Template.Callback()
@log
def _on_album_event(self, evbox, event, data=None):
diff --git a/gnomemusic/widgets/albumeditordialog.py b/gnomemusic/widgets/albumeditordialog.py
new file mode 100644
index 00000000..15a47bf4
--- /dev/null
+++ b/gnomemusic/widgets/albumeditordialog.py
@@ -0,0 +1,113 @@
+# Copyright 2019 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 gettext import gettext as _
+from gi.repository import Gdk, GdkPixbuf, GObject, Gtk
+
+from gnomemusic import log
+from gnomemusic.albumartcache import Art, make_icon_frame
+import gnomemusic.utils as utils
+
+
+@Gtk.Template(resource_path='/org/gnome/Music/ui/AlbumEditorDialog.ui')
+class AlbumEditorDialog(Gtk.Dialog):
+
+ __gtype_name__ = 'AlbumEditorDialog'
+
+ _album_entry = Gtk.Template.Child()
+ _artist_entry = Gtk.Template.Child()
+ _cover_stack = Gtk.Template.Child()
+ _genre_entry = Gtk.Template.Child()
+ _select_button = Gtk.Template.Child()
+ _title_bar = Gtk.Template.Child()
+ _year_entry = Gtk.Template.Child()
+
+ _detail_fields = ['album', 'artist', 'genre', 'year']
+
+ new_cover = GObject.Property(type=str, default=None)
+
+ def __repr__(self):
+ return '<AlbumEditorDialog>'
+
+ @log
+ def __init__(self, parent, album):
+ super().__init__()
+
+ self._album = album
+ self._parent = parent
+ self.props.transient_for = self._parent
+ self.set_titlebar(self._title_bar)
+
+ self._scale = self._parent.get_scale_factor()
+ self._size = Art.Size.LARGE
+ self._cover_stack.props.size = self._size
+ self._cover_stack.update(album)
+
+ self._init_labels()
+
+ @log
+ def _init_labels(self):
+ for field in self._detail_fields:
+ entry = getattr(self, '_' + field + '_entry')
+ value = utils.fields[field](self._album)
+ if value:
+ entry.props.text = value
+
+ @Gtk.Template.Callback()
+ @log
+ def _on_selection(self, select_button):
+ self.response(Gtk.ResponseType.ACCEPT)
+
+ @Gtk.Template.Callback()
+ @log
+ def _on_cancel_button_clicked(self, cancel_button):
+ self.response(Gtk.ResponseType.REJECT)
+
+ @Gtk.Template.Callback()
+ @log
+ def _choose_cover(self, btn):
+ dialog_title = _("Choose album cover")
+ chooser_dialog = Gtk.FileChooserDialog(
+ dialog_title, self._parent, Gtk.FileChooserAction.OPEN,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
+
+ image_filter = Gtk.FileFilter()
+ image_filter.set_name(_("Image"))
+ image_filter.add_mime_type("image/png")
+ image_filter.add_mime_type("image/jpeg")
+ chooser_dialog.add_filter(image_filter)
+
+ response = chooser_dialog.run()
+ if response == Gtk.ResponseType.OK:
+ new_filename = chooser_dialog.get_filename()
+ preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(new_filename)
+ surface = Gdk.cairo_surface_create_from_pixbuf(
+ preview_pixbuf, self._scale, None)
+ surface = _make_icon_frame(surface, self._size, self._scale)
+ self._cover_stack.update_from_surface(surface)
+ self.props.new_cover = new_filename
+ self._select_button.props.sensitive = True
+
+ chooser_dialog.destroy()
diff --git a/gnomemusic/widgets/coverstack.py b/gnomemusic/widgets/coverstack.py
index 4a981193..2efb2ed0 100644
--- a/gnomemusic/widgets/coverstack.py
+++ b/gnomemusic/widgets/coverstack.py
@@ -22,7 +22,7 @@
# 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 GLib, GObject, Gtk
+from gi.repository import GdkPixbuf, GLib, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import Art, DefaultIcon
@@ -117,6 +117,18 @@ class CoverStack(Gtk.Stack):
self._handler_id = self._art.connect('finished', self._art_retrieved)
self._art.lookup()
+ @log
+ def update_from_surface(self, surface):
+ if self._active_child == "B":
+ self._cover_a.props.surface = surface
+ self.props.visible_child_name = "A"
+ else:
+ self._cover_b.props.surface = surface
+ self.props.visible_child_name = "B"
+ self._active_child = self.props.visible_child_name
+
+ self.emit('updated')
+
@log
def _set_loading_child(self):
self.props.visible_child_name = "loading"
@@ -131,14 +143,5 @@ class CoverStack(Gtk.Stack):
GLib.source_remove(self._timeout)
self._timeout = None
- if self._active_child == "B":
- self._cover_a.props.surface = klass.surface
- self.props.visible_child_name = "A"
- else:
- self._cover_b.props.surface = klass.surface
- self.props.visible_child_name = "B"
-
- self._active_child = self.props.visible_child_name
+ self.update_from_surface(klass.surface)
self._art = None
-
- self.emit('updated')
diff --git a/gnomemusic/widgets/selectiontoolbar.py b/gnomemusic/widgets/selectiontoolbar.py
index 1d2b775a..d6995197 100644
--- a/gnomemusic/widgets/selectiontoolbar.py
+++ b/gnomemusic/widgets/selectiontoolbar.py
@@ -33,9 +33,11 @@ class SelectionToolbar(Gtk.ActionBar):
__gtype_name__ = 'SelectionToolbar'
_add_to_playlist_button = Gtk.Template.Child()
+ _edit_cover_button = Gtk.Template.Child()
__gsignals__ = {
- 'add-to-playlist': (GObject.SignalFlags.RUN_FIRST, None, ())
+ 'add-to-playlist': (GObject.SignalFlags.RUN_FIRST, None, ()),
+ 'edit-cover': (GObject.SignalFlags.RUN_FIRST, None, ())
}
selected_items_count = GObject.Property(type=int, default=0, minimum=0)
@@ -44,9 +46,10 @@ class SelectionToolbar(Gtk.ActionBar):
return '<SelectionToolbar>'
@log
- def __init__(self):
+ def __init__(self, window):
super().__init__()
+ self._headerbar = window._headerbar
self.connect(
'notify::selected-items-count', self._on_item_selection_changed)
@@ -55,9 +58,17 @@ class SelectionToolbar(Gtk.ActionBar):
def _on_add_to_playlist_button_clicked(self, widget):
self.emit('add-to-playlist')
+ @Gtk.Template.Callback()
+ @log
+ def _on_edit_cover_button_clicked(self, widget):
+ self.emit('edit-cover')
+
@log
def _on_item_selection_changed(self, widget, data):
- if self.props.selected_items_count > 0:
- self._add_to_playlist_button.props.sensitive = True
- else:
- self._add_to_playlist_button.props.sensitive = False
+ stack = self._headerbar.props.stack
+ albums_view_visible = (stack.props.visible_child_name == 'albums')
+ selection_size = self.props.selected_items_count
+
+ self._add_to_playlist_button.props.sensitive = (selection_size > 0)
+ self._edit_cover_button.props.sensitive = (selection_size == 1
+ and albums_view_visible)
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index 539642a4..c8463f46 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -29,7 +29,7 @@
# 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, Gdk, Gio, GLib, GObject
+from gi.repository import Gtk, Gdk, Gio, GLib, GObject, Grl
from gettext import gettext as _
from gnomemusic import log
@@ -43,6 +43,7 @@ from gnomemusic.views.emptyview import EmptyView
from gnomemusic.views.searchview import SearchView
from gnomemusic.views.songsview import SongsView
from gnomemusic.views.playlistview import PlaylistView
+from gnomemusic.widgets.albumeditordialog import AlbumEditorDialog
from gnomemusic.widgets.headerbar import HeaderBar
from gnomemusic.widgets.notificationspopup import NotificationsPopup
from gnomemusic.widgets.playertoolbar import PlayerToolbar
@@ -52,6 +53,7 @@ from gnomemusic.widgets.selectiontoolbar import SelectionToolbar
from gnomemusic.windowplacement import WindowPlacement
from gnomemusic.playlists import Playlists
from gnomemusic.grilo import grilo
+import gnomemusic.utils as utils
import logging
logger = logging.getLogger(__name__)
@@ -122,7 +124,7 @@ class Window(Gtk.ApplicationWindow):
self._searchbar = Searchbar()
self._player = Player(self)
self._player_toolbar = PlayerToolbar(self._player, self)
- selection_toolbar = SelectionToolbar()
+ selection_toolbar = SelectionToolbar(self)
self.views = [None] * len(View)
self._stack = Gtk.Stack(
transition_type=Gtk.StackTransitionType.CROSSFADE,
@@ -178,6 +180,7 @@ class Window(Gtk.ApplicationWindow):
'toggled', self._on_search_toggled)
selection_toolbar.connect(
'add-to-playlist', self._on_add_to_playlist)
+ selection_toolbar.connect('edit-cover', self._on_edit_cover)
self._headerbar.props.state = HeaderBar.State.MAIN
self._headerbar.show()
@@ -499,6 +502,28 @@ class Window(Gtk.ApplicationWindow):
self._stack.get_visible_child().get_selected_songs(callback)
+ @log
+ def _on_edit_cover(self, widget):
+ current_view = self._stack.get_visible_child()
+ if current_view != self.views[View.ALBUM]:
+ return
+
+ def callback(selected_songs):
+ if len(selected_songs) < 1:
+ return
+
+ album = selected_songs[0]
+ new_cover = None
+ editor_dialog = AlbumEditorDialog(self, album)
+ if editor_dialog.run() == Gtk.ResponseType.ACCEPT:
+ new_cover = editor_dialog.props.new_cover
+
+ current_view.update_cover_from_selection(new_cover)
+ self.props.selection_mode = False
+ editor_dialog.destroy()
+
+ current_view.get_selected_songs(callback)
+
@log
def set_player_visible(self, visible):
"""Set PlayWidget action visibility
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 15aaaf14..bd48f8fd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@ data/org.gnome.Music.appdata.xml.in.in
data/org.gnome.Music.desktop.in.in
data/org.gnome.Music.gschema.xml
data/ui/AboutDialog.ui.in
+data/ui/AlbumEditorDialog.ui
data/ui/AlbumWidget.ui
data/ui/AppMenu.ui
data/ui/HeaderBar.ui
@@ -30,6 +31,7 @@ gnomemusic/views/emptyview.py
gnomemusic/views/playlistview.py
gnomemusic/views/searchview.py
gnomemusic/views/songsview.py
+gnomemusic/widgets/albumeditordialog.py
gnomemusic/widgets/albumwidget.py
gnomemusic/widgets/artistalbumwidget.py
gnomemusic/widgets/disclistboxwidget.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]