Compare commits

...

9 Commits

Author SHA1 Message Date
Kian-Meng Ang 607309fcf6
Fix typos (#64)
Found via `codespell -S po`
2022-11-29 11:19:10 +01:00
Martin Wagner b9a3989893 fixed menu position 2022-11-28 23:19:58 +01:00
Martin Wagner 00cc65f421 formatting changes in PlaylistView 2022-11-28 23:12:16 +01:00
Martin Wagner 39a9245fd6 simplified menus 2022-11-28 23:10:45 +01:00
Martin Wagner c2dd1674f2 removed some hacky global shortcuts 2022-11-28 21:34:59 +01:00
Martin Wagner bf25bfa7bc removed local shortcuts from shortcuts window 2022-11-28 20:19:27 +01:00
Martin Wagner 7cd41deef6 removed some obsolete shortcuts 2022-11-28 20:15:07 +01:00
Martin Wagner 0645816972 replaced popovers by menus 2022-11-28 19:12:34 +01:00
Martin Wagner 0526ce3328 removed focus settings of close_button 2022-11-26 11:18:23 +01:00
3 changed files with 100 additions and 195 deletions

View File

@ -38,6 +38,13 @@
<property name="accelerator">F5</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes">Clear playlist</property>
<property name="accelerator">&lt;Shift&gt;Delete</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
@ -171,60 +178,6 @@
</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 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">&lt;Shift&gt;Delete</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes">Show information</property>
<property name="accelerator">&lt;Control&gt;i Menu</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkShortcutsGroup">
<property name="visible">True</property>
<property name="title" translatable="yes">Search, Album Dialog and Album List</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes">Append</property>
<property name="accelerator">&lt;Control&gt;plus</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes">Play immediately</property>
<property name="accelerator">&lt;Control&gt;p</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes">Show information</property>
<property name="accelerator">&lt;Control&gt;i Menu</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>

View File

@ -10,7 +10,7 @@
<p>Mpdevil is a simple music browser for the Music Player Daemon (MPD) which is focused on playing local music without the need of managing playlists. Instead of maintaining a client side database of your music library mpdevil loads all tags and covers on demand. So you'll never see any outdated information in the browser. Mpdevil strongly relies on tags.</p>
<ul>
<li>Display large covers</li>
<li>Play songs without doubleclick</li>
<li>Play songs without double-click</li>
<li>Fetch lyrics</li>
<li>MPRIS interface</li>
<li>Basic queue manipulation (move and delete single tracks)</li>

View File

@ -868,6 +868,14 @@ class Client(MPDClient):
else:
return None
def show_in_file_manager(self, uri):
if (path:=self.get_absolute_path(uri)) is not None:
file=Gio.File.new_for_path(path)
bus=Gio.bus_get_sync(Gio.BusType.SESSION, None)
proxy=Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, "org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", None)
proxy.call_sync("ShowItems", GLib.Variant("(ass)", ((file.get_uri(),),"")), Gio.DBusCallFlags.NONE, 500, None)
def toggle_play(self):
status=self.status()
if status["state"] == "play":
@ -1258,7 +1266,7 @@ class TreeView(Gtk.TreeView):
return (rect.x+rect.width//2, max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y))
def save_set_cursor(self, *args, **kwargs):
# The standard set_cursor function should scroll normally, but it dosen't work as it should when the treeview is not completely
# The standard set_cursor function should scroll normally, but it doesn't work as it should when the treeview is not completely
# initialized. This usually happens when the program is freshly started and the treeview isn't done with its internal tasks.
# See: https://lazka.github.io/pgi-docs/GLib-2.0/constants.html#GLib.PRIORITY_HIGH_IDLE
# Running set_cursor with a lower priority ensures that the treeview is done before it gets scrolled.
@ -1273,91 +1281,6 @@ class AutoSizedIcon(Gtk.Image):
super().__init__(icon_name=icon_name)
settings.bind(settings_key, self, "pixel-size", Gio.SettingsBindFlags.GET)
class SongPopover(Gtk.Popover):
def __init__(self, client, show_buttons=True):
super().__init__(position=Gtk.PositionType.BOTTOM)
self._client=client
self._rect=Gdk.Rectangle()
self._uri=None
# buttons
hbox=Gtk.Box(spacing=6)
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)
if show_buttons:
button_box=Gtk.ButtonBox(layout_style=Gtk.ButtonBoxStyle.EXPAND)
data=((_("Append"), "list-add-symbolic", "append"), (_("Play"), "media-playback-start-symbolic", "play"))
for tooltip, icon, mode in data:
button=Gtk.Button(tooltip_text=tooltip, image=Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON))
button.connect("clicked", self._on_button_clicked, mode)
button_box.pack_start(button, True, True, 0)
hbox.pack_end(button_box, False, False, 0)
# treeview
# (tag, display-value, tooltip)
self._store=Gtk.ListStore(str, str, str)
self._treeview=Gtk.TreeView(model=self._store, headers_visible=False, search_column=-1, tooltip_column=2, can_focus=False)
self._treeview.get_selection().set_mode(Gtk.SelectionMode.NONE)
# columns
renderer_text=Gtk.CellRendererText(width_chars=50, ellipsize=Pango.EllipsizeMode.MIDDLE, ellipsize_set=True)
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0, weight=Pango.Weight.BOLD)
column_tag=Gtk.TreeViewColumn(_("MPD-Tag"), renderer_text_ralign, text=0)
column_tag.set_property("resizable", False)
self._treeview.append_column(column_tag)
column_value=Gtk.TreeViewColumn(_("Value"), renderer_text, text=1)
column_value.set_property("resizable", False)
self._treeview.append_column(column_value)
# scroll
self._scroll=Gtk.ScrolledWindow(child=self._treeview, propagate_natural_height=True)
self._scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
# connect
self._open_button.connect("clicked", self._on_open_button_clicked)
# packing
frame=Gtk.Frame(child=self._scroll)
vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL, border_width=6, spacing=6)
vbox.pack_start(frame, True, True, 0)
vbox.pack_end(hbox, False, False, 0)
self.add(vbox)
vbox.show_all()
def open(self, uri, widget, x, y):
self._uri=uri
self._rect.x,self._rect.y=x,y
self.set_pointing_to(self._rect)
self.set_relative_to(widget)
window=self.get_toplevel()
self._scroll.set_max_content_height(window.get_size()[1]//2)
self._store.clear()
song=self._client.lsinfo(uri)[0]
for tag, value in song.items():
if tag == "duration":
self._store.append([tag+":", str(value), locale.str(value)])
elif tag in ("last-modified", "format"):
self._store.append([tag+":", str(value), value.raw()])
else:
self._store.append([tag+":", str(value), GLib.markup_escape_text(str(value))])
abs_path=self._client.get_absolute_path(uri)
self._open_button.set_sensitive(abs_path is not None) # show open with button when song is on the same computer
self.popup()
self._treeview.columns_autosize()
def _on_open_button_clicked(self, *args):
self.popdown()
file=Gio.File.new_for_path(self._client.get_absolute_path(self._uri))
bus=Gio.bus_get_sync(Gio.BusType.SESSION, None)
proxy=Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, "org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", None)
proxy.call_sync("ShowItems", GLib.Variant("(ass)", ((file.get_uri(),),"")), Gio.DBusCallFlags.NONE, 500, None)
def _on_button_clicked(self, widget, mode):
self._client.files_to_playlist([self._uri], mode)
self.popdown()
class SongsList(TreeView):
def __init__(self, client):
super().__init__(activate_on_single_click=True, headers_visible=False, enable_search=False, search_column=4)
@ -1400,20 +1323,46 @@ class SongsList(TreeView):
button.connect("clicked", self._on_button_clicked, mode)
self.buttons.pack_start(button, True, True, 0)
# song popover
self._song_popover=SongPopover(self._client)
# menu
action_group=Gio.SimpleActionGroup()
action=Gio.SimpleAction.new("append", None)
action.connect("activate", lambda *args: self._client.files_to_playlist([self._store[self.get_cursor()[0]][3]], "append"))
action_group.add_action(action)
action=Gio.SimpleAction.new("play", None)
action.connect("activate", lambda *args: self._client.files_to_playlist([self._store[self.get_cursor()[0]][3]], "play"))
action_group.add_action(action)
self._show_action=Gio.SimpleAction.new("show", None)
self._show_action.connect("activate", lambda *args: self._client.show_in_file_manager(self._store[self.get_cursor()[0]][3]))
action_group.add_action(self._show_action)
self.insert_action_group("menu", action_group)
menu=Gio.Menu()
menu.append(_("Append"), "menu.append")
menu.append(_("Play"), "menu.play")
subsection=Gio.Menu()
subsection.append(_("Show in file manager"), "menu.show")
menu.append_section(None, subsection)
self._menu=Gtk.Popover.new_from_model(self, menu)
self._menu.set_position(Gtk.PositionType.BOTTOM)
# connect
self.connect("row-activated", self._on_row_activated)
self.connect("button-press-event", self._on_button_press_event)
self.connect("key-press-event", self._on_key_press_event)
def clear(self):
self._song_popover.popdown()
self._menu.popdown()
self._store.clear()
def append(self, track, title, duration, file, search_string=""):
self._store.insert_with_valuesv(-1, range(5), [track, title, duration, file, search_string])
def _open_menu(self, uri, x, y):
rect=Gdk.Rectangle()
rect.x,rect.y=x,y
self._menu.set_pointing_to(rect)
self._show_action.set_enabled(self._client.get_absolute_path(uri) is not None)
self._menu.popup()
def _on_row_activated(self, widget, path, view_column):
self._client.files_to_playlist([self._store[path][3]], "play")
@ -1427,19 +1376,19 @@ class SongsList(TreeView):
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
uri=self._store[path][3]
point=self.convert_bin_window_to_widget_coords(event.x,event.y)
self._song_popover.open(uri, widget, *point)
self._open_menu(uri, *point)
def _on_key_press_event(self, widget, event):
if event.state & Gdk.ModifierType.CONTROL_MASK and event.keyval == Gdk.keyval_from_name("plus"):
if (path:=self.get_cursor()[0]) is not None:
self._client.files_to_playlist([self._store[path][3]], "append")
elif event.keyval == Gdk.keyval_from_name("Menu"):
if (path:=self.get_cursor()[0]) is not None:
self._open_menu(self._store[path][3], *self.get_popover_point(path))
def _on_button_clicked(self, widget, mode):
self._client.files_to_playlist((row[3] for row in self._store), mode)
def show_info(self):
if (path:=self.get_cursor()[0]) is not None:
self._song_popover.open(self._store[path][3], self, *self.get_popover_point(path))
def add_to_playlist(self, mode):
if (path:=self.get_cursor()[0]) is not None:
self._client.files_to_playlist([self._store[path][3]], mode)
##########
# search #
##########
@ -2059,7 +2008,6 @@ class AlbumView(Gtk.Box):
# close button
close_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("go-previous-symbolic", Gtk.IconSize.BUTTON), halign=Gtk.Align.START)
close_button.set_focus_on_click(False)
# connect
self.connect("hide", lambda *args: print("test"))
@ -2228,23 +2176,49 @@ class PlaylistView(TreeView):
self._column_title=columns[1]
self._column_title.set_property("expand", True)
# song popover
self._song_popover=SongPopover(self._client, show_buttons=False)
# menu
action_group=Gio.SimpleActionGroup()
action=Gio.SimpleAction.new("remove", None)
action.connect("activate", lambda *args: self._store.remove(self._store.get_iter(self.get_cursor()[0])))
action_group.add_action(action)
self._show_action=Gio.SimpleAction.new("show", None)
self._show_action.connect("activate", lambda *args: self._client.show_in_file_manager(self._store[self.get_cursor()[0]][3]))
action_group.add_action(self._show_action)
self.insert_action_group("menu", action_group)
menu=Gio.Menu()
menu.append(_("Remove song"), "menu.remove")
menu.append(_("Clean playlist"), "mpd.clean")
menu.append(_("Clear playlist"), "mpd.clear")
subsection=Gio.Menu()
subsection.append(_("Show in file manager"), "menu.show")
menu.append_section(None, subsection)
self._menu=Gtk.Popover.new_from_model(self, menu)
self._menu.set_position(Gtk.PositionType.BOTTOM)
# connect
self.connect("row-activated", self._on_row_activated)
self.connect("button-press-event", self._on_button_press_event)
self.connect("key-release-event", self._on_key_release_event)
self.connect("key-press-event", self._on_key_press_event)
self._row_deleted=self._store.connect("row-deleted", self._on_row_deleted)
self._row_inserted=self._store.connect("row-inserted", self._on_row_inserted)
self._client.emitter.connect("playlist", self._on_playlist_changed)
self._client.emitter.connect("current_song", self._on_song_changed)
self._client.emitter.connect("disconnected", self._on_disconnected)
self._client.emitter.connect("connected", self._on_connected)
def scroll_to_selected_title(self):
if (path:=self.get_property("selected-path")) is not None:
self._scroll_to_path(path)
def _open_menu(self, uri, x, y):
rect=Gdk.Rectangle()
rect.x,rect.y=x,y
self._menu.set_pointing_to(rect)
self._show_action.set_enabled(self._client.get_absolute_path(uri) is not None)
self._menu.popup()
def _clear(self, *args):
self._song_popover.popdown()
self._menu.popdown()
self._playlist_version=None
self.set_property("selected-path", None)
self._store.handler_block(self._row_inserted)
@ -2278,12 +2252,9 @@ class PlaylistView(TreeView):
def _scroll_to_path(self, path):
self.save_scroll_to_cell(path, None, True, 0.25)
def scroll_to_selected_title(self):
if (path:=self.get_property("selected-path")) is not None:
self._scroll_to_path(path)
def _refresh_selection(self): # Gtk.TreePath(len(self._store) is used to generate an invalid TreePath (needed to unset cursor)
self.set_cursor(Gtk.TreePath(len(self._store)), None, False)
if not self._menu.get_visible():
self.set_cursor(Gtk.TreePath(len(self._store)), None, False)
song=self._client.status().get("song")
if song is None:
self._unselect()
@ -2298,15 +2269,15 @@ class PlaylistView(TreeView):
self._delete(path)
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
point=self.convert_bin_window_to_widget_coords(event.x,event.y)
self._song_popover.open(self._store[path][3], widget, *point)
self._open_menu(self._store[path][3], *point)
def _on_key_release_event(self, widget, event):
def _on_key_press_event(self, widget, event):
if event.keyval == Gdk.keyval_from_name("Delete"):
if (path:=self.get_cursor()[0]) is not None:
try:
self._delete(path)
except:
pass
self._delete(path)
elif event.keyval == Gdk.keyval_from_name("Menu"):
if (path:=self.get_cursor()[0]) is not None:
self._open_menu(self._store[path][3], *self.get_popover_point(path))
def _on_row_deleted(self, model, path): # sync treeview to mpd
try:
@ -2335,7 +2306,7 @@ class PlaylistView(TreeView):
def _on_playlist_changed(self, emitter, version):
self._store.handler_block(self._row_inserted)
self._store.handler_block(self._row_deleted)
self._song_popover.popdown()
self._menu.popdown()
self._unselect()
self._client.restrict_tagtypes("track", "title", "artist", "album", "date")
songs=[]
@ -2387,10 +2358,6 @@ class PlaylistView(TreeView):
def _select_function(self, selection, model, path, path_currently_selected):
return (path == self.get_property("selected-path")) == (not path_currently_selected)
def show_info(self):
if (path:=self.get_cursor()[0]) is not None:
self._song_popover.open(self._store[path][3], self, *self.get_popover_point(path))
class PlaylistWindow(Gtk.Overlay):
def __init__(self, client, settings):
super().__init__()
@ -3033,7 +3000,7 @@ class MPDActionGroup(Gio.SimpleActionGroup):
self._client=client
# actions
self._disable_on_stop_data=("next","prev","seek-forward","seek-backward")
self._disable_on_stop_data=("next","prev","seek-forward","seek-backward", "clean")
self._enable_on_reconnect_data=("toggle-play","stop","clear","update","repeat","random","single","consume","single-oneshot")
self._data=self._disable_on_stop_data+self._enable_on_reconnect_data
for name in self._data:
@ -3064,6 +3031,9 @@ class MPDActionGroup(Gio.SimpleActionGroup):
def _on_seek_backward(self, action, param):
self._client.seekcur("-10")
def _on_clean(self, action, param):
self._client.files_to_playlist([self._client.currentsong()["file"]], "enqueue")
def _on_clear(self, action, param):
self._client.clear()
@ -3180,18 +3150,11 @@ class MainWindow(Gtk.ApplicationWindow):
self._size=None # needed for window size saving
# actions
simple_actions_data=(
"settings","connection-settings","stats","help","menu",
"toggle-lyrics","back-to-current-album","toggle-search","show-info"
)
simple_actions_data=("settings","connection-settings","stats","help","menu","toggle-lyrics","back-to-current-album","toggle-search")
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 name in ("append","play"):
action=Gio.SimpleAction.new(name, None)
action.connect("activate", self._on_add_to_playlist, name)
self.add_action(action)
self.add_action(self._settings.create_action("mini-player"))
self.add_action(self._settings.create_action("genre-filter"))
@ -3379,16 +3342,6 @@ class MainWindow(Gtk.ApplicationWindow):
def _on_menu(self, action, param):
self._menu_button.emit("clicked")
def _on_show_info(self, action, param):
widget=self.get_focus()
if hasattr(widget, "show_info") and callable(widget.show_info):
widget.show_info()
def _on_add_to_playlist(self, action, param, mode):
widget=self.get_focus()
if hasattr(widget, "add_to_playlist") and callable(widget.add_to_playlist):
widget.add_to_playlist(mode)
def _on_search_button_toggled(self, button):
if button.get_active():
self._stack.set_visible_child_name("search")
@ -3502,7 +3455,6 @@ 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.show-info", ["<Control>i","Menu"]),("win.append", ["<Control>plus"]),("win.play", ["<Control>p"]),
("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"]),