Skip to content

Instantly share code, notes, and snippets.

@nktknshn
Last active October 9, 2024 07:47
Show Gist options
  • Save nktknshn/581ec96f032ff7569718d76eba262e52 to your computer and use it in GitHub Desktop.
Save nktknshn/581ec96f032ff7569718d76eba262e52 to your computer and use it in GitHub Desktop.
quodlibet player alternaitve playlists picker with autcomplete
from typing import Dict, Set
from gi.repository import Gtk, Gdk
from quodlibet.library.playlist import Playlist, PlaylistLibrary
from quodlibet.plugins.songsmenu import SongsMenuPlugin
from quodlibet import app,library
from quodlibet.qltk.window import Dialog
from quodlibet.qltk.msg import ErrorMessage, Icons
class AddToPlaylistDialog(Dialog):
def __init__(self, parent, playlists, songs):
super().__init__(
title="Add To Playlist",
)
self.playlists = playlists
self.songs = songs
self.picked: Set[str] = set()
self.liststore = Gtk.ListStore(str)
for match in self.playlists:
self.liststore.append([match])
self.completion = Gtk.EntryCompletion()
self.completion.set_model(self.liststore)
self.completion.set_text_column(0)
self.completion.set_inline_selection(True)
self.completion.set_match_func(self.match_playlist_func)
self.songs_listbox = Gtk.ListBox()
self.songs_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
picked_songs_listbox = Gtk.Box()
picked_songs_listbox.pack_start(self.songs_listbox, False, False, 6)
scrolled_picked_songs = Gtk.ScrolledWindow()
scrolled_picked_songs.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
scrolled_picked_songs.set_size_request(800, 500)
scrolled_picked_songs.add(picked_songs_listbox)
artists_set = set[str]()
artists_list = []
for song in songs:
song_row = Gtk.ListBoxRow()
song_row_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
spacing=6,
)
song_row.add(song_row_box)
label_text = f"{song['artist']} - {song['album']} - {song['title']}"
song_row_label = Gtk.Label(label=label_text)
song_row_label.set_halign(Gtk.Align.START)
if song['artist'].lower() not in artists_set:
artists_set.add(song['artist'].lower())
artists_list.append(song['artist'])
song_row_box.pack_start(song_row_label, True, True, 0)
self.songs_listbox.add(song_row)
self.songs_listbox.show_all()
self.picked_listbox = Gtk.ListBox()
self.picked_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
self.add_button('Cancel', Gtk.ResponseType.REJECT)
self.add_button('Apply', Gtk.ResponseType.APPLY)
self.set_default_response(Gtk.ResponseType.APPLY)
self.completion.connect("match-selected", self.on_match_selected)
self.completion.connect("no-matches", self.on_no_matches)
entry_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
spacing=6,
)
self.entry = Gtk.Entry()
self.entry.set_completion(self.completion)
self.entry.connect("key-press-event", self.on_entry_key_pressed)
self.create_playlist_button = Gtk.Button(label="+")
self.create_playlist_button.connect("clicked", self.on_create_playlist_button_clicked)
self.create_playlist_button.set_sensitive(False)
entry_box.pack_start(self.entry, False, False, 0)
entry_box.pack_start(self.create_playlist_button, False, False, 0)
self.grid = Gtk.Grid()
# songs_listbox | entry | +
# --------------|-------------
# entry_artists | picked
lb = scrolled_picked_songs
self.grid.add(lb)
self.entry_artists = Gtk.Entry()
self.entry_artists.set_editable(False)
self.entry_artists.set_text(", ".join(artists_list))
self.grid.attach(lb, 0, 0, 3, 5)
self.grid.attach_next_to(self.entry_artists, lb, Gtk.PositionType.BOTTOM, 1, 1)
# width, height
self.grid.attach_next_to(entry_box, lb, Gtk.PositionType.RIGHT, 1, 1)
self.grid.attach_next_to(self.picked_listbox, entry_box, Gtk.PositionType.BOTTOM, 1, 1)
# self.grid.attach(self.table, 1, 0, 2, 1)
self.vbox.add(self.grid)
self.show_all()
self.entry.grab_focus()
def on_entry_key_pressed(self, entry, event):
if event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.KEY_Return:
self.response(Gtk.ResponseType.APPLY)
return True
if event.keyval != Gdk.KEY_Return:
return False
return self.pick_playlist(self.entry.get_text())
def on_no_matches(self, completion):
text = self.entry.get_text()
if len(text) == 0:
self.create_playlist_button.set_sensitive(False)
return
self.create_playlist_button.set_sensitive(True)
def on_match_selected(self, completion, model, it: Gtk.TreeIter):
picked_item = model.get_value(it, 0)
self.pick_playlist(picked_item)
def match_playlist_func(self, completion, key, iter):
item_text: str = self.liststore.get_value(iter, 0)
return key.lower() in item_text.lower()
def on_create_playlist_button_clicked(self, button):
text = self.entry.get_text()
if len(text) == 0:
return
app.library.playlists.create(text)
print(f"Created {text}")
self.playlists.append(text)
self.pick_playlist(text)
def create_playlist(self, pl_name: str):
pass
def on_remove_clicked(self, button, item_row, pl_name):
if pl_name not in self.picked:
return
self.picked_listbox.remove(item_row)
self.picked.remove(pl_name)
def pick_playlist(self, pl_name: str):
if pl_name not in self.playlists:
return
if pl_name in self.picked:
return
self.picked.add(pl_name)
item_row = Gtk.ListBoxRow()
item_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
spacing=6,
)
item_row.add(item_box)
label = Gtk.Label(label=pl_name)
item_box.pack_start(label, True, True, 0)
remove_button = Gtk.Button(label="x")
remove_button.connect("clicked", self.on_remove_clicked, item_row, pl_name)
item_box.pack_start(remove_button, False, False, 0)
self.picked_listbox.add(item_row)
self.picked_listbox.show_all()
self.entry.set_text("")
self.entry.grab_focus()
class AddToPlaylist(SongsMenuPlugin):
PLUGIN_ICON = Icons.APPLICATION_UTILITIES
PLUGIN_ID = "AddToPlaylist"
PLUGIN_NAME = "Add To Playlist"
PLUGIN_DESC = "Add To Playlist Plugin"
def __init__(self, songs=None, library=None):
super().__init__(songs, library)
submenu = Gtk.Menu()
item = Gtk.MenuItem(label="Add")
submenu.append(item)
if submenu.get_children():
self.set_submenu(submenu)
@property
def playlists_dict(self) -> Dict[str, Playlist]:
return {
pl.name: pl for pl in app.library.playlists
}
@property
def playlists(self) -> list[str]:
return [pl.name for pl in app.library.playlists]
@property
def playlists_sorted(self) -> list[str]:
return sorted(self.playlists)
def add_songs_to_playlists(self, songs, playlists):
pl_dict = self.playlists_dict
for pl_name in playlists:
pl = pl_dict.get(pl_name)
if pl is None:
continue
for song in songs:
has, _ = pl.has_songs([song])
if has:
continue
pl.extend([song])
def plugin_songs(self, songs):
dlg = AddToPlaylistDialog(
parent=self,
playlists=self.playlists_sorted,
songs=songs,
)
if dlg.run() == Gtk.ResponseType.APPLY and len(dlg.picked) > 0:
self.add_songs_to_playlists(songs, dlg.picked)
dlg.destroy()
@classmethod
def PluginPreferences(cls, parent):
vbox = Gtk.VBox(spacing=6)
return vbox
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment