~comcloudway/melon

c57b103e3aa9f47a80cc5744586e65eddc578f40 — Jakob Meier a month ago d680db8
Reorder playlist content using drag&drop

Long press on a feed item to start dragging it.

Hovering over other feed items, outlines them in blue (this happens
automatically and appears to be implemented in gtk).
Additionally, hovering on the top half of feed items adds a margin to
the top of the widget, to indicate that the dragged item would be
inserted before.
Hovering on the bottom half shows the margin on the bottom, to show that
the element will be inserted below.

NOTE: d&d behaves weirdly on touch screens, but by using 2 fingers or a
combination of dragging and clicking it seems to work

BUG: gtk has problems with rendering the hover preview (at least on
wayland/sway)
2 files changed, 102 insertions(+), 6 deletions(-)

M melon/models/__init__.py
M melon/playlist/__init__.py
M melon/models/__init__.py => melon/models/__init__.py +20 -1
@@ 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")



M melon/playlist/__init__.py => melon/playlist/__init__.py +82 -5
@@ 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"))