@@ 865,7 865,7 @@ def get_playlists() -> list[PlaylistWrapper]:
return results
-def add_to_local_playlist(playlist_id: int, vid, pos=None):
+def add_to_local_playlist(playlist_id: int, vid, pos=None, notify=True):
ensure_video(vid)
conn = connect_to_db()
if pos is None:
@@ 887,6 887,25 @@ def add_to_local_playlist(playlist_id: int, vid, pos=None):
(playlist_id, vid.id, vid.server, pos),
)
conn.close()
+ if notify:
+ notify("playlists_changed")
+
+
+def set_local_playlist_content(playlist_id: int, videos: list[Resource]):
+ conn = connect_to_db()
+ # remove old playlist content
+ execute_sql(
+ conn,
+ """
+ DELETE FROM local_playlist_content
+ WHERE id = ?
+ """,
+ (playlist_id,),
+ )
+ # manually add new items
+ for index, entry in enumerate(videos):
+ add_to_local_playlist(playlist_id, entry, pos=index, notify=False)
+ conn.close()
notify("playlists_changed")
@@ 3,12 3,12 @@ import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
-from gi.repository import Gtk, Adw, GLib
+from gi.repository import Gtk, Gdk, Adw, GLib, GObject
from unidecode import unidecode
from gettext import gettext as _
from melon.servers.utils import get_servers_list, get_server_instance
-from melon.servers import Preference, PreferenceType
+from melon.servers import Preference, PreferenceType, Resource
from melon.widgets.iconbutton import IconButton
from melon.widgets.feeditem import AdaptiveFeedItem
from melon.widgets.simpledialog import SimpleDialog
@@ 20,8 20,7 @@ from melon.models import (
ensure_delete_local_playlist,
set_local_playlist_thumbnail,
delete_from_local_playlist,
-)
-from melon.models import (
+ set_local_playlist_content,
is_server_enabled,
ensure_server_disabled,
ensure_server_enabled,
@@ 99,7 98,9 @@ class LocalPlaylistScreen(Adw.NavigationPage):
app_conf = get_app_settings()
self.box.add(group)
# add playlist content to group as well
- for entry, index in sorted(playlist.content, key=lambda x: x[1]):
+ for raw_index, (entry, index) in enumerate(
+ sorted(playlist.content, key=lambda x: x[1])
+ ):
item = AdaptiveFeedItem(entry)
item.menuitems = [
(
@@ 119,6 120,26 @@ class LocalPlaylistScreen(Adw.NavigationPage):
),
),
]
+ drag_source = Gtk.DragSource()
+ drag_source.set_content(
+ Gdk.ContentProvider.new_union(
+ [
+ Gdk.ContentProvider.new_for_value(raw_index),
+ ]
+ )
+ )
+ drag_source.set_actions(Gdk.DragAction.MOVE)
+ drag_source.connect("drag-begin", pass_me(self.entry_drag_begin, item))
+ drag_source.connect("drag-end", self.entry_drag_end)
+ drag_source.connect("drag-cancel", self.entry_drag_cancel)
+ item.add_controller(drag_source)
+ drop_target = Gtk.DropTarget.new(GObject.TYPE_NONE, Gdk.DragAction.MOVE)
+ drop_target.set_gtypes([int])
+ drop_target.connect("drop", pass_me(self.entry_drop, item, raw_index))
+ drop_target.connect("motion", pass_me(self.entry_drop_motion, item))
+ drop_target.connect("leave", pass_me(self.entry_drop_leave, item))
+ item.add_controller(drop_target)
+
group.add(item)
self.scrollview.set_child(self.box)
@@ 126,6 147,62 @@ class LocalPlaylistScreen(Adw.NavigationPage):
self.toolbar_view.set_content(self.wrapper)
self.set_child(self.toolbar_view)
+ def entry_drag_begin(self, source, drag, widget):
+ preview = Gtk.WidgetPaintable.new(widget)
+ source.set_icon(preview, 0, 0)
+
+ def entry_drag_end(self, source, drag, delete_data):
+ source.set_icon(None, 0, 0)
+
+ def entry_drag_cancel(self, source, drag, reason):
+ source.set_icon(None, 0, 0)
+
+ def entry_drop(self, target, value, x, y, widget, index):
+ height = widget.get_height()
+ if value == index:
+ # do nothing
+ # because the item does not need to be moved
+ self.do_update()
+ return
+ if y > height / 2:
+ # insert below
+ target_index = index * 2 + 2
+ else:
+ # insert above
+ target_index = index * 2
+ # create database containing all entries, but spaced two apart
+ # this way, we can easily fit in new entries
+ db = dict(
+ [
+ [x[1] * 2 + 1, x[0]]
+ for x in sorted(self.playlist.content, key=lambda x: x[1])
+ ]
+ )
+ # remove dropped entry
+ entry = db.pop(value * 2 + 1)
+ # insert entry at new position
+ db[target_index] = entry
+ # build updated video list
+ plist = [x[1] for x in sorted(db.items(), key=lambda x: x[0])]
+ # update playlist database using the list indices from the list
+ set_local_playlist_content(self.playlist_id, plist)
+ self.do_update()
+
+ def entry_drop_motion(self, target, x, y, widget):
+ height = widget.get_height()
+ indicator_distance = 10
+ if y > height / 2:
+ widget.set_margin_top(0)
+ widget.set_margin_bottom(indicator_distance)
+ else:
+ widget.set_margin_top(indicator_distance)
+ widget.set_margin_bottom(0)
+ return Gdk.DragAction.MOVE
+
+ def entry_drop_leave(self, target, widget):
+ widget.set_margin_top(0)
+ widget.set_margin_bottom(0)
+
def open_edit(self):
self.edit_diag = SimpleDialog()
self.edit_diag.set_title(_("Edit Playlist"))