Compare commits
7 Commits
da87188a78
...
05b3cc9bbc
Author | SHA1 | Date |
---|---|---|
Martin Wagner | 05b3cc9bbc | |
Martin Wagner | 38f096cd56 | |
Martin Wagner | e46ec34ed5 | |
Martin Wagner | 220cc772b5 | |
Martin Wagner | 563d92f077 | |
Martin Wagner | 8301a05520 | |
Martin Wagner | 911e52b0f7 |
|
@ -13,7 +13,6 @@ Features
|
|||
- MPRIS interface (based on mpDris2)
|
||||
- manage multiple MPD servers
|
||||
- basic queue manipulation (move and delete single tracks)
|
||||
- save and load playlists
|
||||
|
||||
See: https://github.com/SoongNoonien/mpdevil/wiki/Usage
|
||||
|
||||
|
@ -74,5 +73,5 @@ sudo update-desktop-database
|
|||
Translation
|
||||
-----------
|
||||
|
||||
mpdevil is currently available in English, German, Dutch, Bulgarian, Turkish and Polish. If you speak another language, you can easily translate mpdevil by using `poedit`. Just import `po/mpdevil.pot` from this repo into `poedit`. To test your translation, copy the new `.po` file into the `po` directory of your cloned mpdevil repo and proceed as described in the [Building](#building) section. To get your translation integrated into mpdevil, just send me an e-mail or create a pull request. Link to `poedit`: https://poedit.net/
|
||||
mpdevil is currently available in English, German, Dutch, Bulgarian, Turkish and Polish. If you speak one of these or even another language, you can easily translate mpdevil by using `poedit`. Just import `po/mpdevil.pot` from this repo into `poedit`. To test your translation, copy the new `.po` file into the `po` directory of your cloned mpdevil repo and proceed as described in the [Building](#building) section. To get your translation integrated into mpdevil, just send me an e-mail or create a pull request. Link to `poedit`: https://poedit.net/
|
||||
|
||||
|
|
|
@ -51,20 +51,6 @@
|
|||
<object class="GtkShortcutsGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Window</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Cycle through profiles</property>
|
||||
<property name="accelerator"><Control>p</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Cycle through profiles in reversed order</property>
|
||||
<property name="accelerator"><Shift><Control>p</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
|
@ -124,35 +110,35 @@
|
|||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Stop after current title</property>
|
||||
<property name="accelerator"><Control>space</property>
|
||||
<property name="accelerator"><Shift><Control>s</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Next title</property>
|
||||
<property name="accelerator">KP_Add</property>
|
||||
<property name="accelerator"><Alt>Down</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Previous title</property>
|
||||
<property name="accelerator">KP_Subtract</property>
|
||||
<property name="accelerator"><Alt>Up</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Seek forward</property>
|
||||
<property name="accelerator">KP_Multiply</property>
|
||||
<property name="accelerator"><Alt>Right</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Seek backward</property>
|
||||
<property name="accelerator">KP_Divide</property>
|
||||
<property name="accelerator"><Alt>Left</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -166,14 +152,14 @@
|
|||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Toggle random mode</property>
|
||||
<property name="accelerator"><Control>s</property>
|
||||
<property name="accelerator"><Control>n</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Toggle single mode</property>
|
||||
<property name="accelerator"><Control>1</property>
|
||||
<property name="accelerator"><Control>s</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -185,6 +171,60 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Playlist</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Remove selected song</property>
|
||||
<property name="accelerator">Delete</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Clear playlist</property>
|
||||
<property name="accelerator"><Shift>Delete</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Show additional information</property>
|
||||
<property name="accelerator"><Control>i Menu</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Profiles</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Profile 1</property>
|
||||
<property name="accelerator"><Control>1</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Profile 2</property>
|
||||
<property name="accelerator"><Control>2</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Profile 3</property>
|
||||
<property name="accelerator"><Control>3</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="visible">True</property>
|
||||
|
@ -200,7 +240,6 @@
|
|||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Append selected item</property>
|
||||
<property name="subtitle" translatable="yes">Middle-click</property>
|
||||
<property name="accelerator"><Control>plus</property>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -208,7 +247,6 @@
|
|||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Play selected item immediately</property>
|
||||
<property name="subtitle" translatable="yes">Double-click</property>
|
||||
<property name="accelerator"><Control>Return</property>
|
||||
</object>
|
||||
</child>
|
||||
|
@ -216,36 +254,6 @@
|
|||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Show additional information</property>
|
||||
<property name="subtitle" translatable="yes">Right-click</property>
|
||||
<property name="accelerator"><Control>i Menu</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Playlist</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Remove selected song</property>
|
||||
<property name="subtitle" translatable="yes">Middle-click</property>
|
||||
<property name="accelerator">Delete</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Clear playlist</property>
|
||||
<property name="accelerator"><Shift>Delete</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Show additional information</property>
|
||||
<property name="subtitle" translatable="yes">Right-click</property>
|
||||
<property name="accelerator"><Control>i Menu</property>
|
||||
</object>
|
||||
</child>
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<li>MPRIS interface</li>
|
||||
<li>manage multiple mpd servers</li>
|
||||
<li>basic queue manipulation (move and delete single tracks)</li>
|
||||
<li>save and load playlists</li>
|
||||
</ul>
|
||||
</description>
|
||||
<releases>
|
||||
|
|
303
src/mpdevil.py
303
src/mpdevil.py
|
@ -1326,33 +1326,6 @@ class AutoSizedIcon(Gtk.Image):
|
|||
super().__init__(icon_name=icon_name)
|
||||
settings.bind(settings_key, self, "pixel-size", Gio.SettingsBindFlags.GET)
|
||||
|
||||
class AddToPlaylistPopover(Gtk.Popover):
|
||||
def __init__(self, client):
|
||||
super().__init__()
|
||||
self._client=client
|
||||
self.uri=None
|
||||
self._vbox=Gtk.ButtonBox(orientation=Gtk.Orientation.VERTICAL, margin=10, valign=Gtk.Align.START)
|
||||
self._scroll=Gtk.ScrolledWindow(child=self._vbox, propagate_natural_height=True)
|
||||
self._scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
self.connect("show", self._on_show)
|
||||
self.add(self._scroll)
|
||||
|
||||
def _on_show(self, *args):
|
||||
for playlist in self._vbox.get_children():
|
||||
self._vbox.remove(playlist)
|
||||
for playlist in sorted((p["playlist"] for p in self._client.listplaylists()), key=locale.strxfrm):
|
||||
button=Gtk.ModelButton(label=playlist)
|
||||
button.get_child().set_property("xalign", 0)
|
||||
button.connect("clicked", self._on_playlist_clicked, playlist)
|
||||
self._vbox.pack_start(button, True, True, 0)
|
||||
window=self.get_toplevel()
|
||||
self._scroll.set_max_content_height(window.get_size()[1]//2)
|
||||
self._scroll.show_all()
|
||||
|
||||
def _on_playlist_clicked(self, button, name):
|
||||
if self.uri:
|
||||
self._client.playlistadd(name, self.uri)
|
||||
|
||||
class SongPopover(Gtk.Popover):
|
||||
def __init__(self, client, show_buttons=True):
|
||||
super().__init__(position=Gtk.PositionType.BOTTOM)
|
||||
|
@ -1360,14 +1333,8 @@ class SongPopover(Gtk.Popover):
|
|||
self._rect=Gdk.Rectangle()
|
||||
self._uri=None
|
||||
|
||||
# playlists popover
|
||||
self._playlists_popover=AddToPlaylistPopover(self._client)
|
||||
|
||||
# buttons
|
||||
hbox=Gtk.Box(spacing=6)
|
||||
playlist_button=Gtk.MenuButton(image=Gtk.Image.new_from_icon_name("view-list-symbolic", Gtk.IconSize.BUTTON),
|
||||
tooltip_text=_("Add to playlist"), popover=self._playlists_popover)
|
||||
hbox.pack_start(playlist_button, False, False, 0)
|
||||
self._open_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("folder-open-symbolic", Gtk.IconSize.BUTTON),
|
||||
tooltip_text=_("Show in file manager"))
|
||||
hbox.pack_end(self._open_button, False, False, 0)
|
||||
|
@ -1416,7 +1383,6 @@ class SongPopover(Gtk.Popover):
|
|||
|
||||
def open(self, uri, widget, x, y):
|
||||
self._uri=uri
|
||||
self._playlists_popover.uri=uri
|
||||
self._rect.x,self._rect.y=x,y
|
||||
self.set_pointing_to(self._rect)
|
||||
self.set_relative_to(widget)
|
||||
|
@ -1451,7 +1417,7 @@ class SongPopover(Gtk.Popover):
|
|||
class SongsList(TreeView):
|
||||
__gsignals__={"button-clicked": (GObject.SignalFlags.RUN_FIRST, None, ())}
|
||||
def __init__(self, client, width=-1):
|
||||
super().__init__(search_column=-1, activate_on_single_click=True, headers_visible=False)
|
||||
super().__init__(enable_search=False, activate_on_single_click=True, headers_visible=False)
|
||||
self._client=client
|
||||
|
||||
# store
|
||||
|
@ -2330,204 +2296,10 @@ class Browser(Gtk.Paned):
|
|||
# playlist #
|
||||
############
|
||||
|
||||
class PlaylistPopover(Gtk.Popover):
|
||||
__gsignals__={
|
||||
"playlist-changed": (GObject.SignalFlags.RUN_FIRST, None, (str, int, int)),
|
||||
"playlist-deleted": (GObject.SignalFlags.RUN_FIRST, None, (int, int)),
|
||||
}
|
||||
def __init__(self, client):
|
||||
super().__init__()
|
||||
self._client=client
|
||||
self._name=None
|
||||
self._rect=Gdk.Rectangle()
|
||||
|
||||
# buttons
|
||||
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10)
|
||||
data=((_("Append"), "list-add-symbolic", "append"),
|
||||
(_("Play"), "media-playback-start-symbolic", "play"),
|
||||
(_("Enqueue"), "insert-object-symbolic", "enqueue")
|
||||
)
|
||||
for label, icon, mode in data:
|
||||
button=Gtk.ModelButton(label=label, image=Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON))
|
||||
button.get_child().set_property("xalign", 0)
|
||||
button.connect("clicked", self._on_button_clicked, mode)
|
||||
vbox.pack_start(button, True, True, 0)
|
||||
vbox.pack_start(Gtk.Separator(), False, False, 0)
|
||||
save_button=Gtk.ModelButton(label=_("Save"), image=Gtk.Image.new_from_icon_name("document-save-symbolic", Gtk.IconSize.BUTTON))
|
||||
save_button.get_child().set_property("xalign", 0)
|
||||
save_button.connect("clicked", self._on_save_button_clicked)
|
||||
vbox.pack_start(save_button, True, True, 0)
|
||||
delete_button=Gtk.ModelButton(label=_("Delete"), image=Gtk.Image.new_from_icon_name("edit-delete-symbolic", Gtk.IconSize.BUTTON))
|
||||
delete_button.get_child().set_property("xalign", 0)
|
||||
delete_button.connect("clicked", self._on_delete_button_clicked)
|
||||
vbox.pack_start(delete_button, True, True, 0)
|
||||
|
||||
self.add(vbox)
|
||||
vbox.show_all()
|
||||
|
||||
def open(self, name, widget, x, y):
|
||||
self._rect.x=x
|
||||
self._rect.y=y
|
||||
self.set_pointing_to(self._rect)
|
||||
self.set_relative_to(widget)
|
||||
self._name=name
|
||||
self.popup()
|
||||
|
||||
def _on_button_clicked(self, widget, mode):
|
||||
self._client.stored_to_playlist(self._name, mode)
|
||||
self.popdown()
|
||||
|
||||
def _on_delete_button_clicked(self, *args):
|
||||
self._client.rm(self._name)
|
||||
self.popdown()
|
||||
self.emit("playlist-deleted", self._rect.x, self._rect.y)
|
||||
|
||||
def _on_save_button_clicked(self, *args):
|
||||
self._client.rm(self._name)
|
||||
self._client.save(self._name)
|
||||
self.popdown()
|
||||
self.emit("playlist-changed", self._name, self._rect.x, self._rect.y)
|
||||
|
||||
class PlaylistsView(TreeView):
|
||||
def __init__(self, client):
|
||||
super().__init__(search_column=-1, activate_on_single_click=True, headers_visible=False)
|
||||
self._client=client
|
||||
self._selection=self.get_selection()
|
||||
|
||||
# store
|
||||
# display name, name
|
||||
self._store=Gtk.ListStore(str, str)
|
||||
self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
||||
self.set_model(self._store)
|
||||
|
||||
# columns
|
||||
renderer_text=Gtk.CellRendererText(width_chars=80, ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True, ypad=6)
|
||||
column=Gtk.TreeViewColumn("", renderer_text, markup=0)
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||
column.set_property("resizable", False)
|
||||
column.set_property("expand", True)
|
||||
self.append_column(column)
|
||||
|
||||
# popover
|
||||
self._playlist_popover=PlaylistPopover(self._client)
|
||||
|
||||
# connect
|
||||
self.connect("row-activated", self._on_row_activated)
|
||||
self.connect("button-press-event", self._on_button_press_event)
|
||||
self._playlist_popover.connect("playlist-changed", self._on_playlist_changed)
|
||||
self._playlist_popover.connect("playlist-deleted", self._on_playlist_deleted)
|
||||
|
||||
def add(self, name):
|
||||
self._client.save(name)
|
||||
self._store.insert_with_valuesv(-1, range(2), (self._get_display_name(name), name))
|
||||
self.columns_autosize()
|
||||
|
||||
def refresh(self):
|
||||
self._store.clear()
|
||||
for playlist in self._client.listplaylists():
|
||||
self._store.insert_with_valuesv(-1, range(2), (self._get_display_name(playlist["playlist"]), playlist["playlist"]))
|
||||
|
||||
def _get_display_name(self, name):
|
||||
self._client.restrict_tagtypes()
|
||||
metadata=self._client.listplaylistinfo(name)
|
||||
self._client.tagtypes("all")
|
||||
duration=Duration(sum((float(x["duration"]) for x in metadata)))
|
||||
length=len(metadata)
|
||||
translated_string=ngettext("{number} song ({duration})", "{number} songs ({duration})", length)
|
||||
formatted_string=translated_string.format(number=length, duration=duration)
|
||||
return f"<b>{GLib.markup_escape_text(name)}</b> • {GLib.markup_escape_text(formatted_string)}"
|
||||
|
||||
def _on_row_activated(self, widget, path, view_column):
|
||||
self._client.stored_to_playlist(self._store[path][1])
|
||||
|
||||
def _on_button_press_event(self, widget, event):
|
||||
if (path_re:=widget.get_path_at_pos(int(event.x), int(event.y))) is not None:
|
||||
path=path_re[0]
|
||||
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
||||
self._client.stored_to_playlist(self._store[path][1], "play")
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._client.stored_to_playlist(self._store[path][1], "append")
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._playlist_popover.open(self._store[path][1], widget, event.x, event.y)
|
||||
|
||||
def _on_playlist_changed(self, widget, name, x, y):
|
||||
self._store.set(self._store.get_iter(self.get_path_at_pos(x, y)[0]), 0, self._get_display_name(name), 1, name)
|
||||
self.columns_autosize()
|
||||
|
||||
def _on_playlist_deleted(self, widget, x, y):
|
||||
self._store.remove(self._store.get_iter(self.get_path_at_pos(x, y)[0]))
|
||||
self.columns_autosize()
|
||||
|
||||
def show_info(self):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
path=self._store.get_path(treeiter)
|
||||
self._playlist_popover.open(self._store[path][1], self, *self.get_popover_point(path))
|
||||
|
||||
def add_to_playlist(self, mode):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
self._client.stored_to_playlist(self._store.get_value(treeiter, 1), mode)
|
||||
|
||||
class PlaylistsPopover(Gtk.Popover):
|
||||
def __init__(self, client, label):
|
||||
super().__init__(position=Gtk.PositionType.TOP)
|
||||
self._client=client
|
||||
|
||||
# widgets
|
||||
self._entry=Gtk.Entry()
|
||||
self._add_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("list-add-symbolic", Gtk.IconSize.BUTTON), sensitive=False)
|
||||
self._add_button.get_style_context().add_class("suggested-action")
|
||||
clear_button=Gtk.Button(
|
||||
image=Gtk.Image.new_from_icon_name("edit-clear-symbolic", Gtk.IconSize.BUTTON),
|
||||
tooltip_text=_("Clear playlist"),
|
||||
action_name="mpd.clear",
|
||||
can_focus=False
|
||||
)
|
||||
clear_button.get_style_context().add_class("destructive-action")
|
||||
|
||||
# playlists view
|
||||
self._playlists_view=PlaylistsView(self._client)
|
||||
self._scroll=Gtk.ScrolledWindow(child=self._playlists_view, propagate_natural_height=True)
|
||||
self._scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
|
||||
# connect
|
||||
self.connect("show", self._on_show)
|
||||
self._entry.connect("activate", self._add)
|
||||
self._entry.connect("changed", self._on_entry_changed)
|
||||
self._add_button.connect("clicked", self._add)
|
||||
|
||||
# packing
|
||||
hbox0=Gtk.Box(spacing=6)
|
||||
hbox0.pack_start(label, False, False, 6)
|
||||
hbox0.pack_end(clear_button, False, False, 0)
|
||||
hbox=Gtk.Box(spacing=6)
|
||||
hbox.pack_start(self._entry, True, True, 0)
|
||||
hbox.pack_start(self._add_button, False, False, 0)
|
||||
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, border_width=6)
|
||||
vbox.pack_start(hbox, False, False, 0)
|
||||
vbox.pack_end(hbox0, False, False, 0)
|
||||
vbox.pack_end(Gtk.Frame(child=self._scroll), True, True, 0)
|
||||
self.add(vbox)
|
||||
vbox.show_all()
|
||||
|
||||
def _add(self, *args):
|
||||
if self._add_button.get_sensitive():
|
||||
self._playlists_view.add(self._entry.get_text())
|
||||
self._add_button.set_sensitive(False)
|
||||
|
||||
def _on_entry_changed(self, *args):
|
||||
name=self._entry.get_text()
|
||||
self._add_button.set_sensitive(name and not name in (x["playlist"] for x in self._client.listplaylists()))
|
||||
|
||||
def _on_show(self, *args):
|
||||
window=self.get_toplevel()
|
||||
self._scroll.set_max_content_height(window.get_size()[1]//2)
|
||||
self._playlists_view.refresh()
|
||||
self._playlists_view.columns_autosize()
|
||||
|
||||
class PlaylistView(TreeView):
|
||||
selected_path=GObject.Property(type=Gtk.TreePath, default=None) # currently marked song
|
||||
def __init__(self, client, settings):
|
||||
super().__init__(activate_on_single_click=True, reorderable=True, search_column=5, headers_visible=False)
|
||||
super().__init__(activate_on_single_click=True, reorderable=True, search_column=4, headers_visible=False)
|
||||
self._client=client
|
||||
self._settings=settings
|
||||
self._playlist_version=None
|
||||
|
@ -2537,12 +2309,9 @@ class PlaylistView(TreeView):
|
|||
self._selection=self.get_selection()
|
||||
self._selection.set_select_function(self._select_function)
|
||||
|
||||
# label
|
||||
self.label=Gtk.Label(xalign=0, ellipsize=Pango.EllipsizeMode.END)
|
||||
|
||||
# store
|
||||
# (track, title, human duration, file, duration, search)
|
||||
self._store=Gtk.ListStore(str, str, str, str, float, str)
|
||||
# (track, title, duration, file, search)
|
||||
self._store=Gtk.ListStore(str, str, str, str, str)
|
||||
self.set_model(self._store)
|
||||
|
||||
# columns
|
||||
|
@ -2580,7 +2349,6 @@ class PlaylistView(TreeView):
|
|||
|
||||
def _clear(self, *args):
|
||||
self._song_popover.popdown()
|
||||
self._set_playlist_info("")
|
||||
self._playlist_version=None
|
||||
self.set_property("selected-path", None)
|
||||
self._store.handler_block(self._row_inserted)
|
||||
|
@ -2619,9 +2387,6 @@ class PlaylistView(TreeView):
|
|||
path=Gtk.TreePath(int(song))
|
||||
self._select(path)
|
||||
|
||||
def _set_playlist_info(self, text):
|
||||
self.label.set_label(text)
|
||||
|
||||
def _on_button_press_event(self, widget, event):
|
||||
if (path_re:=widget.get_path_at_pos(int(event.x), int(event.y))) is not None:
|
||||
path=path_re[0]
|
||||
|
@ -2668,7 +2433,7 @@ class PlaylistView(TreeView):
|
|||
self._store.handler_block(self._row_deleted)
|
||||
self._song_popover.popdown()
|
||||
self._unselect()
|
||||
self._client.restrict_tagtypes("track", "disc", "title", "artist", "album", "date", "genre")
|
||||
self._client.restrict_tagtypes("track", "title", "artist", "album", "date")
|
||||
songs=[]
|
||||
if self._playlist_version is not None:
|
||||
songs=self._client.plchanges(self._playlist_version)
|
||||
|
@ -2677,37 +2442,22 @@ class PlaylistView(TreeView):
|
|||
self._client.tagtypes("all")
|
||||
if songs:
|
||||
self.freeze_child_notify()
|
||||
self._set_playlist_info("")
|
||||
for song in songs:
|
||||
title=song.get_markup()
|
||||
try:
|
||||
treeiter=self._store.get_iter(song["pos"])
|
||||
except ValueError:
|
||||
self._store.insert_with_valuesv(-1, range(8), [
|
||||
song["track"][0], title,
|
||||
str(song["duration"]), song["file"],
|
||||
float(song["duration"]), song["title"][0]
|
||||
])
|
||||
self._store.insert_with_valuesv(-1, range(5),
|
||||
[song["track"][0], title, str(song["duration"]), song["file"], song["title"][0]]
|
||||
)
|
||||
else:
|
||||
self._store.set(treeiter,
|
||||
0, song["track"][0],
|
||||
1, title,
|
||||
2, str(song["duration"]),
|
||||
3, song["file"],
|
||||
4, float(song["duration"]),
|
||||
5, song["title"][0]
|
||||
0, song["track"][0], 1, title, 2, str(song["duration"]), 3, song["file"], 4, song["title"][0]
|
||||
)
|
||||
self.thaw_child_notify()
|
||||
for i in reversed(range(int(self._client.status()["playlistlength"]), len(self._store))):
|
||||
treeiter=self._store.get_iter(i)
|
||||
self._store.remove(treeiter)
|
||||
playlist_length=len(self._store)
|
||||
if playlist_length == 0:
|
||||
self._set_playlist_info("")
|
||||
else:
|
||||
duration=Duration(sum((row[4] for row in self._store)))
|
||||
translated_string=ngettext("{number} song ({duration})", "{number} songs ({duration})", playlist_length)
|
||||
self._set_playlist_info(translated_string.format(number=playlist_length, duration=duration))
|
||||
self._refresh_selection()
|
||||
if self._playlist_version != version:
|
||||
self.scroll_to_selected_title()
|
||||
|
@ -2746,9 +2496,6 @@ class PlaylistWindow(Gtk.Overlay):
|
|||
)
|
||||
self._treeview=PlaylistView(client, settings)
|
||||
scroll=Gtk.ScrolledWindow(child=self._treeview)
|
||||
self.popover_button=Gtk.MenuButton(image=AutoSizedIcon("view-list-symbolic", "icon-size", settings),
|
||||
popover=PlaylistsPopover(client, self._treeview.label),
|
||||
tooltip_text=_("Playlists"), can_focus=False)
|
||||
|
||||
# connect
|
||||
self._back_to_current_song_button.connect("clicked", self._on_back_to_current_song_button_clicked)
|
||||
|
@ -3543,13 +3290,16 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
# actions
|
||||
simple_actions_data=(
|
||||
"settings","profile-settings","stats","help","menu",
|
||||
"toggle-lyrics","back-to-current-album","toggle-search",
|
||||
"profile-next","profile-prev","show-info"
|
||||
"toggle-lyrics","back-to-current-album","toggle-search","show-info"
|
||||
)
|
||||
for name in simple_actions_data:
|
||||
action=Gio.SimpleAction.new(name, None)
|
||||
action.connect("activate", getattr(self, ("_on_"+name.replace("-","_"))))
|
||||
self.add_action(action)
|
||||
for i, name in enumerate(("profile-1","profile-2","profile-3")):
|
||||
action=Gio.SimpleAction.new(name, None)
|
||||
action.connect("activate", self._on_profile, i)
|
||||
self.add_action(action)
|
||||
for name in ("append","play","enqueue"):
|
||||
action=Gio.SimpleAction.new(name, None)
|
||||
action.connect("activate", self._on_add_to_playlist, name)
|
||||
|
@ -3661,7 +3411,6 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
action_bar.pack_start(playback_control)
|
||||
action_bar.pack_start(seek_bar)
|
||||
action_bar.pack_start(audio)
|
||||
playback_options.pack_start(playlist_window.popover_button, True, True, 0)
|
||||
action_bar.pack_start(playback_options)
|
||||
action_bar.pack_start(volume_button)
|
||||
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
@ -3749,13 +3498,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
|||
def _on_menu(self, action, param):
|
||||
self._menu_button.emit("clicked")
|
||||
|
||||
def _on_profile_next(self, action, param):
|
||||
current_profile=self._settings.get_int("active-profile")
|
||||
self._settings.set_int("active-profile", ((current_profile+1)%3))
|
||||
|
||||
def _on_profile_prev(self, action, param):
|
||||
current_profile=self._settings.get_int("active-profile")
|
||||
self._settings.set_int("active-profile", ((current_profile-1)%3))
|
||||
def _on_profile(self, action, param, profile):
|
||||
self._settings.set_int("active-profile", profile)
|
||||
|
||||
def _on_show_info(self, action, param):
|
||||
widget=self.get_focus()
|
||||
|
@ -3880,14 +3624,13 @@ class mpdevil(Gtk.Application):
|
|||
action_accels=(
|
||||
("app.quit", ["<Control>q"]),("win.mini-player", ["<Control>m"]),("win.help", ["F1"]),("win.menu", ["F10"]),
|
||||
("win.show-help-overlay", ["<Control>question"]),("win.toggle-lyrics", ["<Control>l"]),
|
||||
("win.back-to-current-album", ["Escape"]),("win.toggle-search", ["<control>f"]),
|
||||
("mpd.update", ["F5"]),("mpd.clear", ["<Shift>Delete"]),("mpd.toggle-play", ["space"]),
|
||||
("mpd.stop", ["<Shift>space"]),("mpd.next", ["KP_Add"]),("mpd.prev", ["KP_Subtract"]),
|
||||
("mpd.repeat", ["<Control>r"]),("mpd.random", ["<Control>s"]),("mpd.single", ["<Control>1"]),
|
||||
("mpd.consume", ["<Control>o"]),("mpd.single-oneshot", ["<Control>space"]),("mpd.seek-forward", ["KP_Multiply"]),
|
||||
("mpd.seek-backward", ["KP_Divide"]),("win.profile-next", ["<Control>p"]),("win.profile-prev", ["<Shift><Control>p"]),
|
||||
("win.show-info", ["<Control>i","Menu"]),("win.append", ["<Control>plus"]),
|
||||
("win.play", ["<Control>Return"]),("win.enqueue", ["<Control>e"]),("win.genre-filter", ["<Control>g"])
|
||||
("win.profile-1", ["<Control>1"]),("win.profile-2", ["<Control>2"]),("win.profile-3", ["<Control>3"]),
|
||||
("win.show-info", ["<Control>i","Menu"]),("win.append", ["<Control>plus"]),("win.play", ["<Control>Return"]),
|
||||
("win.enqueue", ["<Control>e"]),("win.genre-filter", ["<Control>g"]),("win.back-to-current-album", ["Escape"]),
|
||||
("win.toggle-search", ["<Control>f"]),("mpd.update", ["F5"]),("mpd.clear", ["<Shift>Delete"]),("mpd.toggle-play", ["space"]),
|
||||
("mpd.stop", ["<Shift>space"]),("mpd.next", ["<Alt>Down"]),("mpd.prev", ["<Alt>Up"]),("mpd.repeat", ["<Control>r"]),
|
||||
("mpd.random", ["<Control>n"]),("mpd.single", ["<Control>s"]),("mpd.consume", ["<Control>o"]),
|
||||
("mpd.single-oneshot", ["<Shift><Control>s"]),("mpd.seek-forward", ["<Alt>Right"]),("mpd.seek-backward", ["<Alt>Left"])
|
||||
)
|
||||
for action, accels in action_accels:
|
||||
self.set_accels_for_action(action, accels)
|
||||
|
|
Loading…
Reference in New Issue