[gnome-music/wip/carlosg/tracker3: 40/68] songeditordialog: Initial songs support



commit 5d5ee3568867f7ad6f1e85396abb7780c5d9fb67
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            | 709 +++++++++++++++++++++++++++++++++
 gnomemusic/widgets/songeditordialog.py | 294 ++++++++++++++
 po/POTFILES.in                         |   2 +
 5 files changed, 1011 insertions(+)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index 00e2ec7a..ade3427f 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: @theme_selected_bg_color;
+}
+
 /* 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 4ef28690..f10ce21b 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -35,6 +35,7 @@
     <file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/SongsView.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..c034714c
--- /dev/null
+++ b/data/ui/SongEditorDialog.ui
@@ -0,0 +1,709 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <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 type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="can_focus">False</property>
+        <property name="title" translatable="yes">Edit Details</property>
+        <property name="show_close_button">True</property>
+        <property name="visible">True</property>
+      </object>
+    </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>
+        </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_label">
+                    <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>
+</interface>
diff --git a/gnomemusic/widgets/songeditordialog.py b/gnomemusic/widgets/songeditordialog.py
new file mode 100644
index 00000000..3943b47c
--- /dev/null
+++ b/gnomemusic/widgets/songeditordialog.py
@@ -0,0 +1,294 @@
+# 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 _, ngettext
+
+from gi.repository import Gtk, Gio, GObject, GLib
+
+from gnomemusic.albumartcache import Art
+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"
+
+    _cover_stack = Gtk.Template.Child()
+    _next_button = Gtk.Template.Child()
+    _notifications_popup = Gtk.Template.Child()
+    _prev_button = Gtk.Template.Child()
+    _spinner = Gtk.Template.Child()
+    _spinner_label = Gtk.Template.Child()
+    _submit_button = Gtk.Template.Child()
+    _url_label = Gtk.Template.Child()
+    _use_suggestion_button = 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()
+
+    __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, application, selected_song):
+        """Initialize the tag editor
+        :param Application application: Application object
+        :param Coresong selected_song: The Song being edited
+        """
+        super().__init__()
+
+        self._log = application.props.log
+
+        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_label.props.label = file_path[baselength:]
+            self._url_label.props.tooltip_text = file_path[baselength:]
+        else:
+            self._url_label.props.label = file_path
+            self._url_label.props.tooltip_text = file_path
+
+        self._url_label.props.has_tooltip = True
+        self._url_label.props.visible = True
+
+    def _start_spinner(self):
+        self._spinner.start()
+        self._spinner_label.props.label = _("Fetching metadata…")
+
+    def _stop_spinner(self):
+        self._spinner.stop()
+        label = _("No suggestions found")
+        if self._suggestions:
+            label = ngettext(
+                "{} suggestion found", "{} suggestions found",
+                len(self._suggestions)).format(len(self._suggestions))
+
+        self._spinner_label.props.label = label
+
+    def _search_tags(self):
+        self._start_spinner()
+        self._coresong.query_musicbrainz_tags(self._tags_found)
+
+    def _suggestion_sort_func(self, media):
+        creation_date = media.get_creation_date()
+        if creation_date:
+            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 not media:
+            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 not self._notification:
+            return
+
+        if self._notification_undo_id:
+            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 dc53fe14..e254c178 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -19,6 +19,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
@@ -43,4 +44,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]