From fab8604fd39d0a0836eb65c39c764e7d915b4268 Mon Sep 17 00:00:00 2001 From: Martin Wagner Date: Wed, 23 Nov 2022 17:42:42 +0100 Subject: [PATCH] replaced AlbumPopover with AlbumView --- src/mpdevil.py | 228 +++++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 100 deletions(-) diff --git a/src/mpdevil.py b/src/mpdevil.py index 4ddeab6..4ec585f 100755 --- a/src/mpdevil.py +++ b/src/mpdevil.py @@ -1365,18 +1365,17 @@ class SongPopover(Gtk.Popover): self.popdown() class SongsList(TreeView): - __gsignals__={"button-clicked": (GObject.SignalFlags.RUN_FIRST, None, ())} - def __init__(self, client, width=-1): - super().__init__(enable_search=False, activate_on_single_click=True, headers_visible=False) + def __init__(self, client): + super().__init__(activate_on_single_click=True, headers_visible=False, enable_search=False, search_column=4) self._client=client # store - # (track, title, duration, file) - self._store=Gtk.ListStore(str, str, str, str) + # (track, title, duration, file, search string) + self._store=Gtk.ListStore(str, str, str, str, str) self.set_model(self._store) # columns - renderer_text=Gtk.CellRendererText(width_chars=width, ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True) + renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True) attrs=Pango.AttrList() attrs.insert(Pango.AttrFontFeatures.new("tnum 1")) renderer_text_ralign_tnum=Gtk.CellRendererText(xalign=1, attributes=attrs, ypad=6) @@ -1417,8 +1416,8 @@ class SongsList(TreeView): self._song_popover.popdown() self._store.clear() - def append(self, track, title, duration, file): - self._store.insert_with_valuesv(-1, range(4), [track, title, duration, file]) + def append(self, track, title, duration, file, search_string=""): + self._store.insert_with_valuesv(-1, range(5), [track, title, duration, file, search_string]) def _on_row_activated(self, widget, path, view_column): self._client.files_to_playlist([self._store[path][3]]) @@ -1437,7 +1436,6 @@ class SongsList(TreeView): def _on_button_clicked(self, widget, mode): self._client.files_to_playlist((row[3] for row in self._store), mode) - self.emit("button-clicked") def show_info(self): if (path:=self.get_cursor()[0]) is not None: @@ -1447,71 +1445,6 @@ class SongsList(TreeView): if (path:=self.get_cursor()[0]) is not None: self._client.files_to_playlist([self._store[path][3]], mode) -class AlbumPopover(Gtk.Popover): - def __init__(self, client, settings): - super().__init__(position=Gtk.PositionType.BOTTOM) - self._client=client - self._settings=settings - self._rect=Gdk.Rectangle() - - # songs list - # sizing needed for correct popover height - self._songs_list=SongsList(self._client, width=60) - - # scroll - self._scroll=Gtk.ScrolledWindow(child=self._songs_list, propagate_natural_height=True) - self._scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - - # label - self._label=Gtk.Label() - - # connect - self._songs_list.connect("button-clicked", lambda *args: self.popdown()) - - # packing - vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL, border_width=6, spacing=6) - hbox=Gtk.Box(spacing=6) - hbox.pack_end(self._songs_list.buttons, False, False, 0) - hbox.pack_start(self._label, False, False, 6) - frame=Gtk.Frame(child=self._scroll) - vbox.pack_start(frame, True, True, 0) - vbox.pack_end(hbox, False, False, 0) - self.add(vbox) - vbox.show_all() - - def open(self, albumartist, album, date, widget, x, y): - self._rect.x=x - self._rect.y=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._songs_list.clear() - tag_filter=("albumartist", albumartist, "album", album, "date", date) - count=self._client.count(*tag_filter) - duration=str(Duration(count["playtime"])) - length=int(count["songs"]) - text=ngettext("{number} song ({duration})", "{number} songs ({duration})", length).format(number=length, duration=duration) - self._label.set_text(text) - self._client.restrict_tagtypes("track", "title", "artist") - songs=self._client.find(*tag_filter) - self._client.tagtypes("all") - for song in songs: - # only show artists =/= albumartist - try: - song["artist"].remove(albumartist) - except ValueError: - pass - artist=str(song['artist']) - if artist == albumartist or not artist: - title_artist=f"{GLib.markup_escape_text(song['title'][0])}" - else: - title_artist=f"{GLib.markup_escape_text(song['title'][0])} • {GLib.markup_escape_text(artist)}" - self._songs_list.append(song["track"][0], title_artist, str(song["duration"]), song["file"]) - self._songs_list.scroll_to_cell(Gtk.TreePath(0), None, False) # clear old scroll position - self.popup() - self._songs_list.columns_autosize() - ########## # search # ########## @@ -1677,7 +1610,9 @@ class SearchWindow(Gtk.Box): ########### class SelectionList(TreeView): - __gsignals__={"item-selected": (GObject.SignalFlags.RUN_FIRST, None, ()), "clear": (GObject.SignalFlags.RUN_FIRST, None, ())} + __gsignals__={"item-selected": (GObject.SignalFlags.RUN_FIRST, None, ()), + "item-reselected": (GObject.SignalFlags.RUN_FIRST, None, ()), + "clear": (GObject.SignalFlags.RUN_FIRST, None, ())} def __init__(self, select_all_string): super().__init__(search_column=0, headers_visible=False, fixed_height_mode=True) self.select_all_string=select_all_string @@ -1765,7 +1700,9 @@ class SelectionList(TreeView): def _on_selection_changed(self, *args): if (treeiter:=self._selection.get_selected()[1]) is not None: - if (path:=self._store.get_path(treeiter)) != self._selected_path: + if (path:=self._store.get_path(treeiter)) == self._selected_path: + self.emit("item-reselected") + else: self._selected_path=path self.emit("item-selected") @@ -1997,6 +1934,7 @@ class AlbumLoadingThread(threading.Thread): idle_add(callback) class AlbumList(Gtk.IconView): + __gsignals__={"show_info": (GObject.SignalFlags.RUN_FIRST, None, (str,str,str,))} def __init__(self, client, settings, artist_list): super().__init__(item_width=0,pixbuf_column=0,markup_column=1,activate_on_single_click=True,selection_mode=Gtk.SelectionMode.BROWSE) self._settings=settings @@ -2012,9 +1950,6 @@ class AlbumList(Gtk.IconView): self.progress_bar=Gtk.ProgressBar(no_show_all=True, valign=Gtk.Align.END, vexpand=False) self.progress_bar.get_style_context().add_class("osd") - # popover - self._album_popover=AlbumPopover(self._client, self._settings) - # cover thread self._cover_thread=AlbumLoadingThread(self._client, self._settings, self.progress_bar, self, self._store, None, None) @@ -2036,7 +1971,6 @@ class AlbumList(Gtk.IconView): def _clear(self, *args): def callback(): - self._album_popover.popdown() self._workaround_clear() if self._cover_thread.is_alive(): self._cover_thread.set_callback(callback) @@ -2104,8 +2038,7 @@ class AlbumList(Gtk.IconView): h=self.get_hadjustment().get_value() if path is not None: tags=self._store[path][3:6] - # when using "button-press-event" in iconview popovers only show up in combination with idle_add (bug in GTK?) - idle_add(self._album_popover.open, *tags, widget, event.x-h, event.y-v) + self.emit("show-info", *tags) def _on_item_activated(self, widget, path): self._path_to_playlist(path) @@ -2118,12 +2051,8 @@ class AlbumList(Gtk.IconView): def show_info(self): if (path:=self.get_cursor()[1]) is not None: - cell=self.get_cell_rect(path, None)[1] - rect=self.get_allocation() - x=max(min(cell.x+cell.width//2, rect.x+rect.width), rect.x) - y=max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y) tags=self._store[path][3:6] - self._album_popover.open(*tags, self, x, y) + self.emit("show-info", *tags) def add_to_playlist(self, mode): if (path:=self.get_cursor()[1]) is not None: @@ -2133,6 +2062,93 @@ class AlbumList(Gtk.IconView): if self._client.connected(): self._refresh() +class AlbumView(Gtk.Box): + __gsignals__={"close": (GObject.SignalFlags.RUN_FIRST, None, ())} + def __init__(self, client, settings): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self._client=client + self._settings=settings + + # songs list + self.songs_list=SongsList(self._client) + self.songs_list.set_enable_search(True) + self.songs_list.buttons.set_halign(Gtk.Align.END) + scroll=Gtk.ScrolledWindow(child=self.songs_list) + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + + # cover + self._cover=Gtk.Image() + size=self._settings.get_int("album-cover")*1.5 + pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size) + self._cover.set_from_pixbuf(pixbuf) + + # labels + self._title=Gtk.Label(margin_start=12, margin_end=12, xalign=0) + self._title.set_line_wrap(True) # wrap=True is not working + self._duration=Gtk.Label(xalign=1, ellipsize=Pango.EllipsizeMode.END) + + # 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")) + close_button.connect("clicked", lambda *args: self.emit("close")) + + # packing + hbox=Gtk.Box(spacing=12) + hbox.pack_end(self.songs_list.buttons, False, False, 0) + hbox.pack_end(self._duration, False, False, 0) + vbox=Gtk.Box(orientation=Gtk.Orientation.VERTICAL, border_width=6) + vbox.pack_start(close_button, False, False, 0) + vbox.set_center_widget(self._title) + vbox.pack_end(hbox, False, False, 0) + header=Gtk.Box() + header.pack_start(self._cover, False, False, 0) + header.pack_start(Gtk.Separator(), False, False, 0) + header.pack_start(vbox, True, True, 0) + self.pack_start(header, False, False, 0) + self.pack_start(Gtk.Separator(), False, False, 0) + self.pack_start(scroll, True, True, 0) + + def display(self, albumartist, album, date): + if date: + self._title.set_markup(f"{GLib.markup_escape_text(album)} ({GLib.markup_escape_text(date)})\n" + f"{GLib.markup_escape_text(albumartist)}") + else: + self._title.set_markup(f"{GLib.markup_escape_text(album)}\n{GLib.markup_escape_text(albumartist)}") + self.songs_list.clear() + tag_filter=("albumartist", albumartist, "album", album, "date", date) + count=self._client.count(*tag_filter) + duration=str(Duration(count["playtime"])) + length=int(count["songs"]) + text=ngettext("{number} song ({duration})", "{number} songs ({duration})", length).format(number=length, duration=duration) + self._duration.set_text(text) + self._client.restrict_tagtypes("track", "title", "artist") + songs=self._client.find(*tag_filter) + self._client.tagtypes("all") + for song in songs: + # only show artists =/= albumartist + try: + song["artist"].remove(albumartist) + except ValueError: + pass + artist=str(song['artist']) + if artist == albumartist or not artist: + title_artist=f"{GLib.markup_escape_text(song['title'][0])}" + else: + title_artist=f"{GLib.markup_escape_text(song['title'][0])} • {GLib.markup_escape_text(artist)}" + self.songs_list.append(song["track"][0], title_artist, str(song["duration"]), song["file"], song["title"][0]) + self.songs_list.save_set_cursor(Gtk.TreePath(0)) + self.songs_list.columns_autosize() + if (cover:=self._client.get_cover({"file": songs[0]["file"], "albumartist": albumartist, "album": album})) is None: + size=self._settings.get_int("album-cover")*1.5 + pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size) + self._cover.set_from_pixbuf(pixbuf) + else: + size=self._settings.get_int("album-cover")*1.5 + self._cover.set_from_pixbuf(cover.get_pixbuf(size)) + class Browser(Gtk.Paned): def __init__(self, client, settings): super().__init__() @@ -2146,6 +2162,16 @@ class Browser(Gtk.Paned): genre_window=Gtk.ScrolledWindow(child=self._genre_list) artist_window=Gtk.ScrolledWindow(child=self._artist_list) album_window=Gtk.ScrolledWindow(child=self._album_list) + self._album_view=AlbumView(self._client, self._settings) + + # album overlay + album_overlay=Gtk.Overlay(child=album_window) + album_overlay.add_overlay(self._album_list.progress_bar) + + # album stack + self._album_stack=Gtk.Stack(transition_type=Gtk.StackTransitionType.SLIDE_LEFT_RIGHT, homogeneous=False) + self._album_stack.add_named(album_overlay, "album_list") + self._album_stack.add_named(self._album_view, "album_view") # hide/show genre filter self._genre_list.set_property("visible", True) @@ -2153,16 +2179,23 @@ class Browser(Gtk.Paned): self._settings.bind("genre-filter", genre_window, "visible", Gio.SettingsBindFlags.GET) self._settings.connect("changed::genre-filter", self._on_genre_filter_changed) + # connect + self._album_list.connect("show-info", self._on_album_list_show_info) + self._album_view.connect("close", lambda *args: self._album_stack.set_visible_child_name("album_list")) + self._artist_list.connect("item-selected", lambda *args: self._album_stack.set_visible_child_name("album_list")) + self._artist_list.connect("item-reselected", lambda *args: self._album_stack.set_visible_child_name("album_list")) + self._client.emitter.connect("disconnected", lambda *args: self._album_stack.set_visible_child_name("album_list")) + self._settings.connect("changed::album-cover", lambda *args: self._album_stack.set_visible_child_name("album_list")) + # packing - album_overlay=Gtk.Overlay(child=album_window) - album_overlay.add_overlay(self._album_list.progress_bar) self.paned1=Gtk.Paned() self.paned1.pack1(artist_window, False, False) - self.paned1.pack2(album_overlay, True, False) + self.paned1.pack2(self._album_stack, True, False) self.pack1(genre_window, False, False) self.pack2(self.paned1, True, False) def back_to_current_album(self): + self._album_stack.set_visible_child_name("album_list") song=self._client.currentsong() artist,genre=self._artist_list.get_artist_selected() if genre is None or song["genre"][0] == genre: @@ -2180,6 +2213,11 @@ class Browser(Gtk.Paned): if not settings.get_boolean(key): self._genre_list.select_all() + def _on_album_list_show_info(self, widget, *tags): + self._album_view.display(*tags) + self._album_stack.set_visible_child_name("album_view") + GLib.idle_add(self._album_view.songs_list.grab_focus) + ############ # playlist # ############ @@ -2533,15 +2571,10 @@ class CoverEventBox(Gtk.EventBox): self._settings=settings self._click_pos=() self.set_events(Gdk.EventMask.POINTER_MOTION_MASK) - - # album popover - self._album_popover=AlbumPopover(self._client, self._settings) - # connect self.connect("button-press-event", self._on_button_press_event) self.connect("button-release-event", self._on_button_release_event) self.connect("motion-notify-event", self._on_motion_notify_event) - self._client.emitter.connect("disconnected", self._on_disconnected) def _on_button_press_event(self, widget, event): if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: @@ -2554,8 +2587,6 @@ class CoverEventBox(Gtk.EventBox): tags=(song["albumartist"][0], song["album"][0], song["date"][0]) if event.button == 1: self._client.album_to_playlist(*tags) - elif event.button == 3: - self._album_popover.open(*tags, widget, event.x, event.y) self._click_pos=() def _on_motion_notify_event(self, widget, event): @@ -2569,9 +2600,6 @@ class CoverEventBox(Gtk.EventBox): window.begin_move_drag(1, event.x_root, event.y_root, Gdk.CURRENT_TIME) self._click_pos=() - def _on_disconnected(self, *args): - self._album_popover.popdown() - class MainCover(Gtk.DrawingArea): def __init__(self, client, settings): super().__init__()