[gnome-music/wip/jfelder/album-cover-editordialog: 8/8] albumeditordialog: Initial cover support



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]