[gnome-music/wip/mschraal/tageditor: 11/21] tageditordialog: Initial songs support



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

    tageditordialog: Initial songs support
    
    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/SelectionToolbar.ui            |  13 +
 data/ui/TagEditorDialog.ui             | 708 +++++++++++++++++++++++++++++++++
 gnomemusic/widgets/selectiontoolbar.py |  41 +-
 gnomemusic/widgets/tageditordialog.py  | 274 +++++++++++++
 gnomemusic/window.py                   |  23 ++
 po/POTFILES.in                         |   2 +
 8 files changed, 1062 insertions(+), 5 deletions(-)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index 9242fc57..b5de896a 100644
--- a/data/org.gnome.Music.css
+++ b/data/org.gnome.Music.css
@@ -24,6 +24,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 0132a33b..32699343 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -28,6 +28,7 @@
     <file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
+    <file preprocess="xml-stripblanks">ui/TagEditorDialog.ui</file>
     <file preprocess="xml-stripblanks">ui/TwoLineTip.ui</file>
     <file preprocess="xml-stripblanks">ui/Window.ui</file>
   </gresource>
diff --git a/data/ui/SelectionToolbar.ui b/data/ui/SelectionToolbar.ui
index ec64876c..4f488e7a 100644
--- a/data/ui/SelectionToolbar.ui
+++ b/data/ui/SelectionToolbar.ui
@@ -17,5 +17,18 @@
         </style>
       </object>
     </child>
+    <child>
+      <object class="GtkButton" id="_edit_details_button">
+        <property name="label" translatable="yes">Edit Details</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_edit_tags_button_clicked" swapped="no"/>
+        <style>
+          <class name="text-button"/>
+        </style>
+      </object>
+    </child>
   </template>
 </interface>
diff --git a/data/ui/TagEditorDialog.ui b/data/ui/TagEditorDialog.ui
new file mode 100644
index 00000000..c4db8d85
--- /dev/null
+++ b/data/ui/TagEditorDialog.ui
@@ -0,0 +1,708 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="TagEditorDialog" 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/widgets/selectiontoolbar.py b/gnomemusic/widgets/selectiontoolbar.py
index 16c9472b..799c56bb 100644
--- a/gnomemusic/widgets/selectiontoolbar.py
+++ b/gnomemusic/widgets/selectiontoolbar.py
@@ -33,9 +33,11 @@ class SelectionToolbar(Gtk.ActionBar):
     __gtype_name__ = 'SelectionToolbar'
 
     _add_to_playlist_button = Gtk.Template.Child()
+    _edit_details_button = Gtk.Template.Child()
 
     __gsignals__ = {
-        'add-to-playlist': (GObject.SignalFlags.RUN_FIRST, None, ())
+        'add-to-playlist': (GObject.SignalFlags.RUN_FIRST, None, ()),
+        "edit-details": (GObject.SignalFlags.RUN_FIRST, None, ())
     }
 
     selected_items_count = GObject.Property(type=int, default=0, minimum=0)
@@ -47,6 +49,8 @@ class SelectionToolbar(Gtk.ActionBar):
     def __init__(self):
         super().__init__()
 
+        self._stack = None
+
         self.connect(
             'notify::selected-items-count', self._on_item_selection_changed)
 
@@ -57,9 +61,36 @@ class SelectionToolbar(Gtk.ActionBar):
     def _on_add_to_playlist_button_clicked(self, widget):
         self.emit('add-to-playlist')
 
+    @Gtk.Template.Callback()
+    def _on_edit_tags_button_clicked(self, widget):
+        self.emit("edit-details")
+
     @log
     def _on_item_selection_changed(self, widget, data):
-        if self.props.selected_items_count > 0:
-            self._add_to_playlist_button.props.sensitive = True
-        else:
-            self._add_to_playlist_button.props.sensitive = False
+        selection_size = self.props.selected_items_count
+
+        self._add_to_playlist_button.props.sensitive = (selection_size > 0)
+        self._edit_details_button.props.sensitive = (selection_size == 1)
+
+    # FIXME: This is a workaround for not being able to pass the stack
+    # object via init when using Gtk.Builder.
+    @GObject.Property(type=Gtk.Stack, default=None)
+    def stack(self):
+        """The main GtkStack of the app
+
+        :return: main stack object
+        :rtype: GtkStack
+        """
+        return self._stack
+
+    @stack.setter
+    def stack(self, stack):
+        """Set the GtkStack object used
+
+        :param GtkStack stack: The GtkStack to use
+        """
+        if (stack is None
+                or (self._stack is not None
+                    and self._stack != stack)):
+            return
+        self._stack = stack
diff --git a/gnomemusic/widgets/tageditordialog.py b/gnomemusic/widgets/tageditordialog.py
new file mode 100644
index 00000000..4f3c9cea
--- /dev/null
+++ b/gnomemusic/widgets/tageditordialog.py
@@ -0,0 +1,274 @@
+# Copyright 2019 The GNOME Music Developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music.  This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered.  If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so.  If you do not wish to do so,
+# delete this exception statement from your version.
+
+import logging
+from gettext import gettext as _
+
+from gi.repository import Grl, Gtk, Gio, GObject, GLib
+
+from gnomemusic.albumartcache import Art
+from gnomemusic.widgets.notificationspopup import TagEditorNotification
+
+import gnomemusic.utils as utils
+
+logger = logging.getLogger(__name__)
+
+
+@Gtk.Template(resource_path="/org/gnome/Music/ui/TagEditorDialog.ui")
+class TagEditorDialog(Gtk.Dialog):
+    """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__ = "TagEditorDialog"
+
+    _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, ()),
+    }
+
+    def __repr__(self):
+        return "<TagEditorDialog>"
+
+    def __init__(self, parent, selected_song, grilo, coreselection):
+        """Initialize the tag editor
+        :param parent: The parent widget calling the editor dialog box
+        :param selected_song: The current selected track which is being edited
+        """
+        super().__init__()
+
+        self._grilo = grilo
+        self._coreselection = coreselection
+
+        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._pointer = -1
+        self._search_tags()
+
+    def _init_labels(self):
+        for field in utils.fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            value = utils.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…"))
+        new_media = Grl.Media.audio_new()
+        new_media.set_url(self._coresong.props.url)
+        self._grilo.get_tags_from_musicbrainz(new_media, 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:
+            logger.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._pointer = 0
+            self._update_suggestion()
+            self._on_entries_changed()
+
+    def _update_suggestion(self):
+        media = self._suggestions[self._pointer]
+        for field in utils.fields_getter:
+            suggestion = getattr(self, "_" + field + "_suggestion")
+            value = utils.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._pointer < len(self._suggestions) - 1)
+        self._prev_button.props.sensitive = (self._pointer > 0)
+
+    def _on_entries_changed(self, widget=None, param=None):
+        if self._pointer >= 0:
+            media = self._suggestions[self._pointer]
+            self._use_suggestion_button.props.sensitive = False
+        self._submit_button.props.sensitive = False
+
+        for field in utils.fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            value = utils.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._pointer >= 0:
+                suggested_value = utils.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._pointer += 1
+        self._update_suggestion()
+        self._on_entries_changed()
+
+    @Gtk.Template.Callback()
+    def _on_prev_button_clicked(self, widget):
+        self._pointer -= 1
+        self._update_suggestion()
+        self._on_entries_changed()
+
+    @Gtk.Template.Callback()
+    def _on_use_suggestion_clicked(self, widget):
+        suggested_media = self._suggestions[self._pointer]
+        self._previous_tags.clear()
+        for field in utils.fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            self._previous_tags[field] = entry.props.text
+            suggested_value = utils.fields_getter[field](suggested_media)
+            if suggested_value:
+                entry.props.text = suggested_value
+
+        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 utils.fields_getter:
+            entry = getattr(self, "_" + field + "_entry")
+            entry.props.text = self._previous_tags[field]
+
+        self._delete_notification()
+
+    @Gtk.Template.Callback()
+    def _on_submit_clicked(self, widget):
+        self._delete_notification()
+
+        for field in self._coresong.fields_setter:
+            entry = getattr(self, "_" + field + "_entry")
+            entry_text = entry.props.text
+            if entry_text:
+                self._coresong.fields_setter[field](entry_text)
+
+        self.destroy()
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index a7750a65..7ffd44be 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -44,6 +44,7 @@ from gnomemusic.widgets.playertoolbar import PlayerToolbar
 from gnomemusic.widgets.playlistdialog import PlaylistDialog
 from gnomemusic.widgets.searchheaderbar import SearchHeaderBar
 from gnomemusic.widgets.selectiontoolbar import SelectionToolbar  # noqa: F401
+from gnomemusic.widgets.tageditordialog import TagEditorDialog
 from gnomemusic.windowplacement import WindowPlacement
 
 import logging
@@ -134,6 +135,8 @@ class Window(Gtk.ApplicationWindow):
         self._player_toolbar = PlayerToolbar()
         self._player_toolbar.props.player = self._player
 
+        self._selection_toolbar.props.stack = self._stack
+
         self._headerbar.connect(
             'back-button-clicked', self._switch_back_from_childview)
 
@@ -180,6 +183,7 @@ class Window(Gtk.ApplicationWindow):
 
         self._selection_toolbar.connect(
             'add-to-playlist', self._on_add_to_playlist)
+        self._selection_toolbar.connect("edit-details", self._on_edit_tags)
         self._search.connect("notify::state", self._on_search_state_changed)
 
         self._headerbar.props.state = HeaderBar.State.MAIN
@@ -526,3 +530,22 @@ class Window(Gtk.ApplicationWindow):
         :param bool visible: actionbar visibility
         """
         self._player_toolbar.set_visible(visible)
+
+    @log
+    def _on_edit_tags(self, widget):
+        if self._stack.get_visible_child() == self.views[View.PLAYLIST]:
+            return
+
+        selected_songs = self._app._coreselection.props.selected_items
+
+        if len(selected_songs) < 1:
+            return
+
+        tags_editor_dialog = TagEditorDialog(self, selected_songs[0],
+                                             self._app.props.coremodel._grilo,
+                                             self._app._coreselection)
+        if tags_editor_dialog.run() == Gtk.ResponseType.ACCEPT:
+            return
+
+        self.props.selection_mode = False
+        tags_editor_dialog.destroy()
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]