[gnome-music/wip/mschraal/tageditor-13feb2020: 1/4] songeditordialog: Initial songs support



commit f09bb8c9de9f2ae74ca1626fcb5ff19ba8360f4b
Author: Sumaid Syed <sumaidsyed gmail com>
Date:   Sun Aug 4 23:02:28 2019 +0530

    songeditordialog: Initial songs support
    
    When a song is selected, it enables to display a dialog to edit its
    main tags (title, artist, album, etc). An online search (with
    chromaprint and acoustid sources) is automatically triggered to
    identify the song from the MusicBrainz database.
    
    An user can choose to use the online suggestions or not. If an online
    suggestion is used, the MusicBrainz tags are also stored.
    
    Based on the patches by Jean Felder
    
    https://gitlab.gnome.org/GNOME/gnome-music/issues/293

 data/org.gnome.Music.css               |   5 +
 data/org.gnome.Music.gresource.xml     |   1 +
 data/ui/SongEditorDialog.ui            | 708 +++++++++++++++++++++++++++++++++
 gnomemusic/utils.py                    |  10 -
 gnomemusic/widgets/songeditordialog.py | 296 ++++++++++++++
 po/POTFILES.in                         |   2 +
 6 files changed, 1012 insertions(+), 10 deletions(-)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index ee821f79..eaf88225 100644
--- a/data/org.gnome.Music.css
+++ b/data/org.gnome.Music.css
@@ -29,6 +29,11 @@
     padding: 12px 0;
 }
 
+/* Suggested Tags in Tag Editor Dialog*/
+.suggestion-label {
+    color: #3680DB;
+}
+
 /* ArtistAlbumsWidget */
 box#ArtistAlbumsWidget .artist-label {
     font-weight: bold;
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index 80ea92f9..84a14a2b 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -29,6 +29,7 @@
     <file preprocess="xml-stripblanks">ui/SearchView.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
+    <file preprocess="xml-stripblanks">ui/SongEditorDialog.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
     <file preprocess="xml-stripblanks">ui/TwoLineTip.ui</file>
     <file preprocess="xml-stripblanks">ui/Window.ui</file>
diff --git a/data/ui/SongEditorDialog.ui b/data/ui/SongEditorDialog.ui
new file mode 100644
index 00000000..c14251be
--- /dev/null
+++ b/data/ui/SongEditorDialog.ui
@@ -0,0 +1,708 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="SongEditorDialog" 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 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>
+        </child>
+        <child>
+          <object class="GtkOverlay" id="overlay">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child type="overlay">
+              <object class="NotificationsPopup" id="_notifications_popup">
+                <property name="halign">center</property>
+                <property name="transition_type">slide-down</property>
+                <property name="valign">start</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkGrid" id="media_details">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="margin">12</property>
+                <property name="row_spacing">5</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <property name="vexpand">True</property>
+                    <property name="valign">fill</property>
+                    <child>
+                      <object class="GtkBox" id="coverart_box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="valign">fill</property>
+                        <property name="margin_right">20</property>
+                        <child>
+                          <object class="CoverStack" id="_cover_stack">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">fill</property>
+                          </object>
+                        </child>
+                        <style>
+                          <class name="album-cover"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="width">1</property>
+                    <property name="height">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="location">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="justify">right</property>
+                    <property name="margin_right">20</property>
+                    <property name="label" translatable="yes">Location</property>
+                    <property name="xalign">1</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">0</property>
+                    <property name="top_attach">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="_url">
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="xalign">0</property>
+                    <property name="max_width_chars">45</property>
+                    <property name="wrap">True</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="top_attach">4</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>
+                      <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="title">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_right">36</property>
+                                <property name="label" translatable="yes">Title</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="_title_suggestion">
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="ellipsize">end</property>
+                                <property name="max_width_chars">30</property>
+                                <style>
+                                  <class name="suggestion-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>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="_title_entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="width_chars">45</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </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="ellipsize">end</property>
+                                <property name="max_width_chars">30</property>
+                                <style>
+                                  <class name="suggestion-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>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="_album_entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="width_chars">45</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </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="ellipsize">end</property>
+                                <property name="max_width_chars">30</property>
+                                <style>
+                                  <class name="suggestion-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>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="_artist_entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="width_chars">45</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="halign">fill</property>
+                        <property name="homogeneous">True</property>
+                        <property name="spacing">12</property>
+                        <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>
+                            <property name="halign">start</property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <child>
+                                  <object class="GtkLabel" id="track">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="label" translatable="yes">Song</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="_track_suggestion">
+                                    <property name="can_focus">False</property>
+                                    <property name="halign">end</property>
+                                    <style>
+                                      <class name="suggestion-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>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="_track_entry">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="width_chars">10</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>
+                        </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>
+                            <property name="halign">start</property>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <child>
+                                  <object class="GtkLabel" id="disc">
+                                    <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">Disc</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="_disc_suggestion">
+                                    <property name="can_focus">False</property>
+                                    <property name="halign">start</property>
+                                    <property name="margin_right">2</property>
+                                    <style>
+                                      <class name="suggestion-label"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="_disc_entry">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="width_chars">10</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>
+                        </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>
+                            <property name="halign">start</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>
+                                    <style>
+                                      <class name="suggestion-label"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkEntry" id="_year_entry">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="width_chars">10</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>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_bottom">20</property>
+            <property name="margin_left">20</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">end</property>
+                <property name="spacing">6</property>
+                <property name="baseline_position">bottom</property>
+                <child>
+                  <object class="GtkSpinner" id="_spinner">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="active">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="_spinner_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Fetching metadata...</property>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="hexpand">True</property>
+                <property name="spacing">6</property>
+                <property name="margin_right">10</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="visible">True</property>
+                        <property name="sensitive">False</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">end</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="icon_name">go-next-symbolic-rtl</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton">
+                        <property name="visible">True</property>
+                        <property name="sensitive">False</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="halign">start</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="icon_name">go-next-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <style>
+                      <class name="linked"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="_prev_next_box">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="homogeneous">True</property>
+                    <style>
+                      <class name="linked"/>
+                    </style>
+                    <child>
+                      <object class="GtkButton" id="_prev_button">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="valign">center</property>
+                        <property name="sensitive">False</property>
+                        <property name="tooltip_text" translatable="yes">Previous Suggestion</property>
+                        <signal name="clicked" handler="_on_prev_button_clicked" swapped="no"/>
+                        <style>
+                            <class name="image-button"/>
+                        </style>
+                        <child>
+                          <object class="GtkImage" id="_prev_button_image">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="icon_name">go-previous-symbolic</property>
+                            <property name="icon_size">1</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="_next_button">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="valign">center</property>
+                        <property name="sensitive">False</property>
+                        <property name="tooltip_text" translatable="yes">Next Suggestion</property>
+                        <signal name="clicked" handler="_on_next_button_clicked" swapped="no"/>
+                        <style>
+                            <class name="image-button"/>
+                        </style>
+                        <child>
+                          <object class="GtkImage" id="_next_button_image">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="icon_name">go-next-symbolic</property>
+                            <property name="icon_size">1</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="_use_suggestion_button">
+                    <property name="label" translatable="yes">Use Suggestion</property>
+                    <property name="visible">True</property>
+                    <property name="sensitive">False</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="_on_use_suggestion_clicked" swapped="no"/>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                    <property name="pack_type">end</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="_submit_button">
+                    <property name="label" translatable="yes">Submit</property>
+                    <property name="visible">True</property>
+                    <property name="sensitive">False</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="_on_submit_clicked" swapped="no"/>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                    <property name="pack_type">end</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          </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 Details</property>
+    <property name="show_close_button">True</property>
+  </object>
+</interface>
diff --git a/gnomemusic/utils.py b/gnomemusic/utils.py
index a8aef985..fe2798e6 100644
--- a/gnomemusic/utils.py
+++ b/gnomemusic/utils.py
@@ -210,13 +210,3 @@ def natural_sort_names(name_a, name_b):
                 for tmp in re.split(r"(\d+)", normalize_caseless(text))]
 
     return _extract_numbers(name_b) < _extract_numbers(name_a)
-
-
-fields_getter = {
-    "album": get_album_title,
-    "artist": get_song_artist,
-    "disc": get_album_disc_nr,
-    "title": get_media_title,
-    "track": get_media_track_nr,
-    "year": get_media_year
-}
diff --git a/gnomemusic/widgets/songeditordialog.py b/gnomemusic/widgets/songeditordialog.py
new file mode 100644
index 00000000..3e534862
--- /dev/null
+++ b/gnomemusic/widgets/songeditordialog.py
@@ -0,0 +1,296 @@
+# Copyright 2020 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 Gtk, Gio, GObject, GLib
+
+from gnomemusic.albumartcache import Art
+from gnomemusic.musiclogger import MusicLogger
+from gnomemusic.widgets.notificationspopup import TagEditorNotification
+import gnomemusic.utils as utils
+
+
+@Gtk.Template(resource_path="/org/gnome/Music/ui/SongEditorDialog.ui")
+class SongEditorDialog(Gtk.Dialog):
+    """Song tag editor widget
+    A tag editor dialog box that allows storing metadata for music files
+    through editing entries manually or applying automatic fetched tags.
+    """
+
+    __gtype_name__ = "SongEditorDialog"
+
+    _notifications_popup = Gtk.Template.Child()
+
+    _cover_stack = Gtk.Template.Child()
+    _spinner = Gtk.Template.Child()
+    _spinner_label = Gtk.Template.Child()
+    _title_bar = Gtk.Template.Child()
+
+    # tags entries and labels
+    _album_entry = Gtk.Template.Child()
+    _album_suggestion = Gtk.Template.Child()
+    _artist_entry = Gtk.Template.Child()
+    _artist_suggestion = Gtk.Template.Child()
+    _disc_entry = Gtk.Template.Child()
+    _disc_suggestion = Gtk.Template.Child()
+    _title_entry = Gtk.Template.Child()
+    _title_suggestion = Gtk.Template.Child()
+    _track_entry = Gtk.Template.Child()
+    _track_suggestion = Gtk.Template.Child()
+    _year_entry = Gtk.Template.Child()
+    _year_suggestion = Gtk.Template.Child()
+    _prev_button = Gtk.Template.Child()
+    _next_button = Gtk.Template.Child()
+    _use_suggestion_button = Gtk.Template.Child()
+    _submit_button = Gtk.Template.Child()
+
+    _url = Gtk.Template.Child()
+
+    __gsignals__ = {
+        "no-tags": (GObject.SignalFlags.RUN_FIRST, None, ()),
+    }
+
+    _fields_getter = {
+        "album": utils.get_album_title,
+        "artist": utils.get_song_artist,
+        "disc": utils.get_album_disc_nr,
+        "title": utils.get_media_title,
+        "track": utils.get_media_track_nr,
+        "year": utils.get_media_year
+    }
+
+    def __init__(self, parent, selected_song, grilo):
+        """Initialize the tag editor
+        :param Window parent: Main window
+        :param Coresong selected_song: The Song being edited
+        :param CoreGrilo grilo: CoreGrilo object
+        """
+        super().__init__()
+
+        self._grilo = grilo
+        self._log = MusicLogger()
+
+        self.props.transient_for = parent
+        self.set_titlebar(self._title_bar)
+
+        self._cover_stack.props.size = Art.Size.LARGE
+        self._cover_stack.update(selected_song)
+
+        music_dir = GLib.UserDirectory.DIRECTORY_MUSIC
+        self._music_directory = GLib.get_user_special_dir(music_dir)
+
+        self._coresong = selected_song
+        self._init_labels()
+
+        self._notification = None
+        self._notification_finished_id = None
+        self._notification_undo_id = None
+
+        self._previous_tags = {}
+        self._suggestions = []
+        self._suggestion_index = -1
+        self._chosen_suggestion_index = -1
+        self._search_tags()
+
+    def _init_labels(self):
+        for field in self._fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            value = self._fields_getter[field](self._coresong.props.media)
+            if value:
+                entry.props.text = value
+            entry.connect("notify::text", self._on_entries_changed)
+
+        file_ = Gio.File.new_for_uri(self._coresong.props.url)
+        file_path = file_.get_path()
+        if file_path.startswith(self._music_directory):
+            baselength = len(self._music_directory) + 1
+            self._url.set_text(file_path[baselength:])
+            self._url.props.tooltip_text = file_path[baselength:]
+        else:
+            self._url.set_text(file_path)
+            self._url.props.tooltip_text = file_path
+
+        self._url.props.has_tooltip = True
+        self._url.props.visible = True
+
+    def _start_spinner(self, text):
+        self._spinner.start()
+        self._spinner_label.props.label = text
+
+    def _stop_spinner(self):
+        self._spinner.stop()
+        self._spinner_label.props.label = ""
+
+    def _search_tags(self):
+        self._start_spinner(_("Fetching metadata…"))
+        self._coresong.query_musicbrainz_tags(self._tags_found)
+
+    def _suggestion_sort_func(self, media):
+        creation_date = media.get_creation_date()
+        if creation_date is not None:
+            return (creation_date.get_year(), media.get_album())
+        return (GLib.DateTime.new_now_utc().get_year(), media.get_album())
+
+    def _tags_found(self, media, count=0):
+        if media is None:
+            self._log.warning("Unable to find tags for song {}".format(
+                self._coresong.props.url))
+            self._stop_spinner()
+            self._create_notification(TagEditorNotification.Type.NONE)
+            return
+
+        self._suggestions.append(media)
+
+        if count == 0:
+            self._suggestions.sort(key=self._suggestion_sort_func)
+            self._stop_spinner()
+            self._suggestion_index = 0
+            self._update_suggestion()
+            self._on_entries_changed()
+
+    def _update_suggestion(self):
+        media = self._suggestions[self._suggestion_index]
+        for field in self._fields_getter:
+            suggestion = getattr(self, "_" + field + "_suggestion")
+            value = self._fields_getter[field](media)
+            if value:
+                suggestion.props.label = value
+                suggestion.props.visible = True
+                suggestion.props.has_tooltip = True
+                suggestion.props.tooltip_text = value
+
+        self._next_button.props.sensitive = (
+            self._suggestion_index < len(self._suggestions) - 1)
+        self._prev_button.props.sensitive = (self._suggestion_index > 0)
+
+    def _on_entries_changed(self, widget=None, param=None):
+        if self._suggestion_index >= 0:
+            media = self._suggestions[self._suggestion_index]
+            self._use_suggestion_button.props.sensitive = False
+        self._submit_button.props.sensitive = False
+
+        for field in self._fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            value = self._fields_getter[field](self._coresong.props.media)
+            typed_value = entry.props.text.strip()
+            if (typed_value
+                    and value != typed_value):
+                self._submit_button.props.sensitive = True
+            if self._suggestion_index >= 0:
+                suggested_value = self._fields_getter[field](media)
+                if (typed_value
+                        and suggested_value
+                        and typed_value != suggested_value):
+                    self._use_suggestion_button.props.sensitive = True
+
+    @Gtk.Template.Callback()
+    def _on_next_button_clicked(self, widget):
+        self._suggestion_index += 1
+        self._update_suggestion()
+        self._on_entries_changed()
+
+    @Gtk.Template.Callback()
+    def _on_prev_button_clicked(self, widget):
+        self._suggestion_index -= 1
+        self._update_suggestion()
+        self._on_entries_changed()
+
+    @Gtk.Template.Callback()
+    def _on_use_suggestion_clicked(self, widget):
+        suggested_media = self._suggestions[self._suggestion_index]
+        self._previous_tags.clear()
+        for field in self._fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            self._previous_tags[field] = entry.props.text
+            suggested_value = self._fields_getter[field](suggested_media)
+            if suggested_value:
+                entry.props.text = suggested_value
+
+        self._chosen_suggestion_index = self._suggestion_index
+        self._create_notification(TagEditorNotification.Type.SONG)
+        self._on_entries_changed()
+
+    def _create_notification(self, notification_type):
+        self._notification = TagEditorNotification(
+            self._notifications_popup, notification_type)
+        if notification_type != TagEditorNotification.Type.NONE:
+            self._notification_undo_id = self._notification.connect(
+                "undo-fill", self._undo_use_suggestion)
+
+        self._notification_finished_id = self._notification.connect(
+            "finished", self._delete_notification)
+
+    def _delete_notification(self, notification=None):
+        if self._notification is None:
+            return
+
+        if self._notification_undo_id is not None:
+            self._notification.disconnect(self._notification_undo_id)
+            self._notification_undo_id = None
+
+        self._notification.disconnect(self._notification_finished_id)
+        self._notification_finished_id = None
+        self._notification = None
+
+    def _undo_use_suggestion(self, notification):
+        """Revert tags filling
+
+        :param TagEditorNotification notification: the notification
+        """
+        for field in self._fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            entry.props.text = self._previous_tags[field]
+
+        self._chosen_suggestion_index = -1
+        self._delete_notification()
+
+    @Gtk.Template.Callback()
+    def _on_submit_clicked(self, widget):
+        self._delete_notification()
+
+        tags = {
+            "album-artist": None,
+            "mb-recording-id": None,
+            "mb-track-id": None,
+            "mb-artist-id": None,
+            "mb-release-id": None,
+            "mb-release-group-id": None
+        }
+
+        for field in self._fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            tags[field] = entry.props.text
+
+        if self._chosen_suggestion_index > -1:
+            media = self._suggestions[self._chosen_suggestion_index]
+            tags["album-artist"] = media.get_album_artist()
+            tags["mb-recording-id"] = media.get_mb_recording_id()
+            tags["mb-track-id"] = media.get_mb_track_id()
+            tags["mb-artist-id"] = media.get_mb_artist_id()
+            tags["mb-release-id"] = media.get_mb_release_id()
+            tags["mb-release-group-id"] = media.get_mb_release_group_id()
+
+        self._coresong.update_tags(tags)
+        self.response(Gtk.ResponseType.ACCEPT)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2df13b28..fd81907e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ data/ui/SearchHeaderBar.ui
 data/ui/SearchView.ui
 data/ui/SelectionBarMenuButton.ui
 data/ui/SelectionToolbar.ui
+data/ui/TagEditorDialog.ui
 gnomemusic/__init__.py
 gnomemusic/albumartcache.py
 gnomemusic/application.py
@@ -39,4 +40,5 @@ gnomemusic/widgets/playertoolbar.py
 gnomemusic/widgets/playlistcontrols.py
 gnomemusic/widgets/playlistdialog.py
 gnomemusic/widgets/starhandlerwidget.py
+gnomemusic/widgets/tageditordialog.py
 gnomemusic/window.py


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]