[gnome-music/wip/jfelder/playlistsview-dnd] playlistsview: Restore drag and drop operation



commit 94731fb1d1613e23969529afbe58d110017d35ab
Author: Jean Felder <jfelder src gnome org>
Date:   Tue Jul 9 21:16:41 2019 +0200

    playlistsview: Restore drag and drop operation

 data/ui/SongWidget.ui                           | 24 ++++++++--
 gnomemusic/grilowrappers/grltrackerplaylists.py | 43 ++++++++++++++++++
 gnomemusic/views/playlistsview.py               | 15 +++++--
 gnomemusic/widgets/disclistboxwidget.py         |  2 -
 gnomemusic/widgets/songwidget.py                | 58 ++++++++++++++++++++++++-
 5 files changed, 133 insertions(+), 9 deletions(-)
---
diff --git a/data/ui/SongWidget.ui b/data/ui/SongWidget.ui
index 28f4675a..8e1987a6 100644
--- a/data/ui/SongWidget.ui
+++ b/data/ui/SongWidget.ui
@@ -6,11 +6,29 @@
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <signal name="notify::selected" handler="_on_selection_changed"/>
+    <signal name="drag_data_received" handler="_on_drag_data_received"/>
     <child>
       <object class="GtkBox" id="box1">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="spacing">3</property>
+       <child>
+          <object class="GtkEventBox" id="_dnd_eventbox">
+            <property name="visible">False</property>
+            <signal name="drag-begin" handler="_on_drag_begin"/>
+            <signal name="drag-end" handler="_on_drag_end"/>
+            <signal name="drag_data_get" handler="_on_drag_data_get"/>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">open-menu-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
         <child>
           <object class="GtkBox" id="box3">
             <property name="width_request">48</property>
@@ -61,7 +79,7 @@
             </child>
           </object>
           <packing>
-            <property name="position">0</property>
+            <property name="position">1</property>
           </packing>
         </child>
         <child>
@@ -103,7 +121,7 @@
             </child>
           </object>
           <packing>
-            <property name="position">1</property>
+            <property name="position">2</property>
           </packing>
         </child>
         <child>
@@ -127,7 +145,7 @@
             </child>
           </object>
           <packing>
-            <property name="position">2</property>
+            <property name="position">3</property>
           </packing>
         </child>
         <child>
diff --git a/gnomemusic/grilowrappers/grltrackerplaylists.py b/gnomemusic/grilowrappers/grltrackerplaylists.py
index 4553f5a7..94a3975d 100644
--- a/gnomemusic/grilowrappers/grltrackerplaylists.py
+++ b/gnomemusic/grilowrappers/grltrackerplaylists.py
@@ -549,6 +549,49 @@ class Playlist(GObject.GObject):
             self._tracker.update_blank_async(
                 query, GLib.PRIORITY_LOW, None, _requery_media, coresong)
 
+    def reorder(self, previous_position, new_position):
+        """Changes the order of a songs in the playlist.
+
+        :param int previous_position: preivous song position
+        :param int new_position: new song position
+        """
+        def _position_changed_cb(conn, res, position):
+            # FIXME: Check for failure.
+            conn.update_finish(res)
+
+        coresong = self._model.get_item(previous_position)
+        self._model.remove(previous_position)
+        self._model.insert(new_position, coresong)
+
+        main_query = """
+        INSERT OR REPLACE {
+        ?entry
+            nfo:listPosition %(position)s
+        }
+        WHERE {
+            ?playlist a nmm:Playlist ;
+                      a nfo:MediaList ;
+                        nfo:hasMediaFileListEntry ?entry .
+            FILTER (
+                tracker:id(?playlist) = %(playlist_id)s &&
+                tracker:id(?entry) = %(song_id)s
+            )
+        }
+        """.replace("\n", " ").strip()
+
+        first_pos = min(previous_position, new_position)
+        last_pos = max(previous_position, new_position)
+
+        for position in range(first_pos, last_pos + 1):
+            coresong = self._model.get_item(position)
+            query = main_query % {
+                "playlist_id": self.props.pl_id,
+                "song_id": coresong.props.media.get_id(),
+                "position": position
+            }
+            self._tracker.update_async(
+                query,  GLib.PRIORITY_LOW, None, _position_changed_cb,
+                position)
 
 class SmartPlaylist(Playlist):
     """Base class for smart playlists"""
diff --git a/gnomemusic/views/playlistsview.py b/gnomemusic/views/playlistsview.py
index 0d423188..fca4c6ea 100644
--- a/gnomemusic/views/playlistsview.py
+++ b/gnomemusic/views/playlistsview.py
@@ -230,7 +230,8 @@ class PlaylistsView(BaseView):
         if self.rename_active:
             self._pl_ctrls.disable_rename_playlist()
 
-        self._view.bind_model(playlist.props.model, self._create_song_widget)
+        self._view.bind_model(
+            playlist.props.model, self._create_song_widget, playlist)
 
         self._current_playlist = playlist
         self._pl_ctrls.props.playlist_name = playlist_name
@@ -243,10 +244,13 @@ class PlaylistsView(BaseView):
     def _on_song_count_changed(self, playlist, value):
         self._update_songs_count(playlist.props.count)
 
-    def _create_song_widget(self, coresong):
-        song_widget = SongWidget(coresong)
+    def _create_song_widget(self, coresong, playlist):
+        can_dnd = not playlist.props.is_smart
+        song_widget = SongWidget(coresong, can_dnd)
 
         song_widget.connect('button-release-event', self._song_activated)
+        if can_dnd is True:
+            song_widget.connect("widget_moved", self._on_song_widget_moved)
 
         return song_widget
 
@@ -299,6 +303,11 @@ class PlaylistsView(BaseView):
         #     self.player.stop()
         #     self._window.set_player_visible(False)
 
+    def _on_song_widget_moved(self, target, source_position):
+        target_position = target.get_parent().get_index()
+        current_playlist = self._sidebar.get_selected_row().playlist
+        current_playlist.reorder(source_position, target_position)
+
     @log
     def _populate(self, data=None):
         """Populate sidebar.
diff --git a/gnomemusic/widgets/disclistboxwidget.py b/gnomemusic/widgets/disclistboxwidget.py
index 02c3c1a6..7de001d4 100644
--- a/gnomemusic/widgets/disclistboxwidget.py
+++ b/gnomemusic/widgets/disclistboxwidget.py
@@ -139,8 +139,6 @@ class DiscBox(Gtk.Box):
 
         song_widget.connect('button-release-event', self._song_activated)
 
-        song_widget.show_all()
-
         return song_widget
 
     @log
diff --git a/gnomemusic/widgets/songwidget.py b/gnomemusic/widgets/songwidget.py
index 35b59fa8..cc12a022 100644
--- a/gnomemusic/widgets/songwidget.py
+++ b/gnomemusic/widgets/songwidget.py
@@ -53,6 +53,7 @@ class SongWidget(Gtk.EventBox):
 
     __gsignals__ = {
         'selection-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
+        "widget-moved": (GObject.SignalFlags.RUN_FIRST, None, (int,))
     }
 
     coresong = GObject.Property(type=CoreSong, default=None)
@@ -63,6 +64,7 @@ class SongWidget(Gtk.EventBox):
 
     _playlists = Playlists.get_default()
 
+    _dnd_eventbox = Gtk.Template.Child()
     _select_button = Gtk.Template.Child()
     _number_label = Gtk.Template.Child()
     _title_label = Gtk.Template.Child()
@@ -82,7 +84,12 @@ class SongWidget(Gtk.EventBox):
         return '<SongWidget>'
 
     @log
-    def __init__(self, coresong):
+    def __init__(self, coresong, can_dnd=False):
+        """Instanciates a SongWidget
+
+        :param Corsong coresong: song associated with the widget
+        :param bool can_dnd: allow drag and drop operations
+        """
         super().__init__()
 
         self.props.coresong = coresong
@@ -134,11 +141,60 @@ class SongWidget(Gtk.EventBox):
 
         self._number_label.props.no_show_all = True
 
+        if can_dnd is True:
+            self._dnd_eventbox.props.visible = True
+            self._drag_widget = None
+            entries = [
+                Gtk.TargetEntry.new(
+                    "GTK_EVENT_BOX", Gtk.TargetFlags.SAME_APP, 0)
+            ]
+            self._dnd_eventbox.drag_source_set(
+                Gdk.ModifierType.BUTTON1_MASK, entries,
+                Gdk.DragAction.MOVE)
+            self.drag_dest_set(
+                Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
+
     @Gtk.Template.Callback()
     @log
     def _on_selection_changed(self, klass, value):
         self.emit('selection-changed')
 
+    @Gtk.Template.Callback()
+    def _on_drag_begin(self, klass, context):
+        gdk_window = self.get_window()
+        _, x, y, _ = gdk_window.get_device_position(context.get_device())
+        allocation = self.get_allocation()
+
+        self._drag_widget = Gtk.ListBox()
+        self._drag_widget.set_size_request(allocation.width, allocation.height)
+
+        drag_row = SongWidget(self.props.coresong)
+        self._drag_widget.add(drag_row)
+        self._drag_widget.drag_highlight_row(drag_row.get_parent())
+        self._drag_widget.show_all()
+        Gtk.drag_set_icon_widget(context, self._drag_widget, x, y)
+
+    @Gtk.Template.Callback()
+    def _on_drag_end(self, klass, context):
+        self._drag_widget = None
+
+    @Gtk.Template.Callback()
+    def _on_drag_data_get(self, klass, context, selection_data, info, time_):
+        row_position = self.get_parent().get_index()
+        selection_data.set(
+            Gdk.Atom.intern("row_position", False), 0,
+            bytes(str(row_position), encoding="UTF8"))
+
+    @Gtk.Template.Callback()
+    def _on_drag_data_received(
+            self, klass, context, x, y, selection_data, info, time_):
+        source_position = int(str(selection_data.get_data(), "UTF-8"))
+        target_position = self.get_parent().get_index()
+        if source_position == target_position:
+            return
+
+        self.emit("widget-moved", source_position)
+
     @Gtk.Template.Callback()
     @log
     def _on_star_toggle(self, widget, event):


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