diff --git a/bin/mpdevil b/bin/mpdevil index cab2d19..b4fc395 100755 --- a/bin/mpdevil +++ b/bin/mpdevil @@ -1287,9 +1287,9 @@ class AboutDialog(Gtk.AboutDialog): self.set_copyright("\xa9 2020 Martin Wagner") self.set_logo_icon_name("mpdevil") -################################# -# small general purpose widgets # -################################# +########################### +# general purpose widgets # +########################### class AutoSizedIcon(Gtk.Image): def __init__(self, icon_name, settings_key, settings): @@ -1341,10 +1341,10 @@ class SongPopover(Gtk.Popover): super().__init__() rect=Gdk.Rectangle() rect.x=x - # Gtk places popovers 26px above the given position for no obvious reasons, so I move them 26px + # Gtk places popovers in treeviews 26px above the given position for no obvious reasons, so I move them 26px rect.y=y+26 - rect.width = 1 - rect.height = 1 + rect.width=1 + rect.height=1 self.set_pointing_to(rect) self.set_relative_to(relative) @@ -1384,53 +1384,6 @@ class SongPopover(Gtk.Popover): frame.show_all() -class Cover(object): - def __init__(self, settings, raw_song): - self.path=None - song=ClientHelper.song_to_first_str_dict(raw_song) - if song != {}: - song_file=song["file"] - active_profile=settings.get_int("active-profile") - lib_path=settings.get_value("paths")[active_profile] - if lib_path == "": - lib_path=GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC) - if lib_path is not None: - regex_str=settings.get_value("regex")[active_profile] - if regex_str == "": - regex=re.compile(COVER_REGEX, flags=re.IGNORECASE) - else: - regex_str=regex_str.replace("%AlbumArtist%", song.get("albumartist", "")) - regex_str=regex_str.replace("%Album%", song.get("album", "")) - try: - regex=re.compile(regex_str, flags=re.IGNORECASE) - except: - print("illegal regex:", regex_str) - return - if song_file is not None: - song_dir=os.path.join(lib_path, os.path.dirname(song_file)) - if song_dir.endswith(".cue"): - song_dir=os.path.dirname(song_dir) # get actual directory of .cue file - if os.path.exists(song_dir): - for f in os.listdir(song_dir): - if regex.match(f): - self.path=os.path.join(song_dir, f) - break - - def get_pixbuf(self, size): - if self.path is None: # fallback needed - path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename() - return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size) - else: - try: - return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size) - except: # load fallback if cover can't be loaded (GLib: Couldn’t recognize the image file format for file...) - path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename() - return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size) - -########### -# browser # -########### - class SongsView(Gtk.TreeView): def __init__(self, client, store, file_column_id): super().__init__(model=store, search_column=-1) @@ -1507,7 +1460,7 @@ class SongsView(Gtk.TreeView): self.handler_unblock(self._key_press_event) class SongsWindow(Gtk.Box): - def __init__(self, client, store, file_column_id): + def __init__(self, client, store, file_column_id, focus_indicator=True): super().__init__(orientation=Gtk.Orientation.VERTICAL) # adding vars @@ -1517,9 +1470,9 @@ class SongsWindow(Gtk.Box): self._songs_view=SongsView(client, store, file_column_id) # scroll - scroll=Gtk.ScrolledWindow() - scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - scroll.add(self._songs_view) + self._scroll=Gtk.ScrolledWindow() + self._scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self._scroll.add(self._songs_view) # buttons append_button=Gtk.Button.new_with_mnemonic(_("_Append")) @@ -1544,10 +1497,13 @@ class SongsWindow(Gtk.Box): enqueue_button.connect("clicked", self._on_enqueue_button_clicked) # packing - frame=FocusFrame() - frame.set_widget(self._songs_view) - frame.add(scroll) - self.pack_start(frame, True, True, 0) + if focus_indicator: + frame=FocusFrame() + frame.set_widget(self._songs_view) + frame.add(self._scroll) + self.pack_start(frame, True, True, 0) + else: + self.pack_start(self._scroll, True, True, 0) button_box.pack_start(append_button, True, True, 0) button_box.pack_start(play_button, True, True, 0) button_box.pack_start(enqueue_button, True, True, 0) @@ -1560,6 +1516,9 @@ class SongsWindow(Gtk.Box): def get_action_bar(self): return self._action_bar + def get_scroll(self): + return self._scroll + def _on_append_button_clicked(self, *args): self._client.wrapped_call("files_to_playlist", self._songs_view.get_files(), "append") @@ -1569,6 +1528,123 @@ class SongsWindow(Gtk.Box): def _on_enqueue_button_clicked(self, *args): self._client.wrapped_call("files_to_playlist", self._songs_view.get_files(), "enqueue") +class AlbumPopover(Gtk.Popover): + def __init__(self, client, settings, album, album_artist, year, relative, x, y): + super().__init__() + rect=Gdk.Rectangle() + rect.x=x + rect.y=y + rect.width=1 + rect.height=1 + self.set_pointing_to(rect) + self.set_relative_to(relative) + + # adding vars + self._client=client + self._settings=settings + songs=self._client.wrapped_call("find", "album", album, "date", year, self._settings.get_artist_type(), album_artist) + + # store + # (track, title (artist), duration, file) + store=Gtk.ListStore(str, str, str, str) + for s in songs: + song=ClientHelper.song_to_list_dict(ClientHelper.pepare_song_for_display(s)) + track=song["track"][0] + title=(", ".join(song["title"])) + # only show artists =/= albumartist + try: + song["artist"].remove(album_artist) + except: + pass + artist=(", ".join(song["artist"])) + if artist == album_artist or artist == "": + title_artist="{}".format(title) + else: + title_artist="{} - {}".format(title, artist) + title_artist=title_artist.replace("&", "&") + store.append([track, title_artist, song["human_duration"][0], song["file"][0]]) + + # songs window + songs_window=SongsWindow(self._client, store, 3, focus_indicator=False) + + # scroll + scroll=songs_window.get_scroll() + scroll.set_max_content_height(self._settings.get_int("height")//3) # TODO + scroll.set_propagate_natural_height(True) + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroll.set_property("margin-start", 3) + scroll.set_property("margin-end", 3) + scroll.set_property("margin-top", 3) + + # songs view + songs_view=songs_window.get_treeview() + + # columns + renderer_text=Gtk.CellRendererText(width_chars=80, ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True) + renderer_text_ralign=Gtk.CellRendererText(xalign=1.0) + column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0) + column_track.set_property("resizable", False) + songs_view.append_column(column_track) + column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, markup=1) + column_title.set_property("resizable", False) + column_title.set_property("expand", True) + songs_view.append_column(column_title) + column_time=Gtk.TreeViewColumn(_("Length"), renderer_text_ralign, text=2) + column_time.set_property("resizable", False) + songs_view.append_column(column_time) + + # packing + self.add(songs_window) + + songs_window.show_all() + +class Cover(object): + def __init__(self, settings, raw_song): + self.path=None + song=ClientHelper.song_to_first_str_dict(raw_song) + if song != {}: + song_file=song["file"] + active_profile=settings.get_int("active-profile") + lib_path=settings.get_value("paths")[active_profile] + if lib_path == "": + lib_path=GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC) + if lib_path is not None: + regex_str=settings.get_value("regex")[active_profile] + if regex_str == "": + regex=re.compile(COVER_REGEX, flags=re.IGNORECASE) + else: + regex_str=regex_str.replace("%AlbumArtist%", song.get("albumartist", "")) + regex_str=regex_str.replace("%Album%", song.get("album", "")) + try: + regex=re.compile(regex_str, flags=re.IGNORECASE) + except: + print("illegal regex:", regex_str) + return + if song_file is not None: + song_dir=os.path.join(lib_path, os.path.dirname(song_file)) + if song_dir.endswith(".cue"): + song_dir=os.path.dirname(song_dir) # get actual directory of .cue file + if os.path.exists(song_dir): + for f in os.listdir(song_dir): + if regex.match(f): + self.path=os.path.join(song_dir, f) + break + + def get_pixbuf(self, size): + if self.path is None: # fallback needed + path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename() + return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size) + else: + try: + return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size) + except: # load fallback if cover can't be loaded (GLib: Couldn’t recognize the image file format for file...) + path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename() + return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size) + +########### +# browser # +########### + class SearchWindow(Gtk.Box): def __init__(self, client): super().__init__(orientation=Gtk.Orientation.VERTICAL) @@ -1864,112 +1940,14 @@ class ArtistWindow(FocusFrame): def _on_show_initials_changed(self, *args): self._column_initials.set_visible(self._settings.get_boolean("show-initials")) -class AlbumDialog(Gtk.Dialog): # also used by 'MainCover' - def __init__(self, parent, client, settings, album, album_artist, year): - use_csd=settings.get_boolean("use-csd") - if use_csd: - super().__init__(transient_for=parent, use_header_bar=True) - else: - super().__init__(transient_for=parent) - - # adding vars - self._client=client - self._settings=settings - songs=self._client.wrapped_call("find", "album", album, "date", year, self._settings.get_artist_type(), album_artist) - - # determine size - size=parent.get_size() - diagonal=(size[0]**2+size[1]**2)**(0.5) - h=diagonal//4 - w=h*5//4 - self.set_default_size(w, h) - - # title - duration=ClientHelper.calc_display_length(songs) - if use_csd: - if year == "": - self.set_title(album) - else: - self.set_title("{} ({})".format(album, year)) - header_bar=self.get_header_bar() - header_bar.set_subtitle("{} ({})".format(album_artist, duration)) - else: - if year == "": - self.set_title("{} - {} ({})".format(album, album_artist, duration)) - else: - self.set_title("{} ({}) - {} ({})".format(album, year, album_artist, duration)) - - # store - # (track, title (artist), duration, file) - store=Gtk.ListStore(str, str, str, str) - for s in songs: - song=ClientHelper.song_to_list_dict(ClientHelper.pepare_song_for_display(s)) - track=song["track"][0] - title=(", ".join(song["title"])) - # only show artists =/= albumartist - try: - song["artist"].remove(album_artist) - except: - pass - artist=(", ".join(song["artist"])) - if artist == album_artist or artist == "": - title_artist="{}".format(title) - else: - title_artist="{} - {}".format(title, artist) - title_artist=title_artist.replace("&", "&") - store.append([track, title_artist, song["human_duration"][0], song["file"][0]]) - - # songs window - songs_window=SongsWindow(self._client, store, 3) - - # songs view - songs_view=songs_window.get_treeview() - - # columns - renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True) - renderer_text_ralign=Gtk.CellRendererText(xalign=1.0) - column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0) - column_track.set_property("resizable", False) - songs_view.append_column(column_track) - column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, markup=1) - column_title.set_property("resizable", False) - column_title.set_property("expand", True) - songs_view.append_column(column_title) - column_time=Gtk.TreeViewColumn(_("Length"), renderer_text_ralign, text=2) - column_time.set_property("resizable", False) - songs_view.append_column(column_time) - - # close button - close_button=Gtk.ToggleButton(image=Gtk.Image.new_from_icon_name("window-close", Gtk.IconSize.BUTTON), label=_("Close")) - - # action bar - action_bar=songs_window.get_action_bar() - action_bar.pack_end(close_button) - - # connect - close_button.connect("clicked", self._on_close_button_clicked) - - # packing - vbox=self.get_content_area() - vbox.set_property("border-width", 0) - vbox.pack_start(songs_window, True, True, 0) - self.show_all() - - def open(self): - response=self.run() - - def _on_close_button_clicked(self, *args): - self.destroy() - class AlbumWindow(FocusFrame): - def __init__(self, client, settings, artist_window, window): + def __init__(self, client, settings, artist_window): super().__init__() # adding vars self._settings=settings self._client=client self._artist_window=artist_window - self._window=window self._button_event=(None, None) self.stop_flag=False self._done=True @@ -1987,6 +1965,8 @@ class AlbumWindow(FocusFrame): scroll=Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.add(self._iconview) + self._scroll_vadj=scroll.get_vadjustment() + self._scroll_hadj=scroll.get_hadjustment() # connect self._iconview.connect("item-activated", self._on_item_activated) @@ -2146,15 +2126,6 @@ class AlbumWindow(FocusFrame): artist=self._store[path][6] self._client.wrapped_call("album_to_playlist", album, artist, year, mode) - def _open_album_dialog(self, path): - if self._client.connected(): - album=self._store[path][4] - year=self._store[path][5] - artist=self._store[path][6] - album_dialog=AlbumDialog(self._window, self._client, self._settings, album, artist, year) - album_dialog.open() - album_dialog.destroy() - def _done_callback(self, *args): self.stop_flag=False self._done=True @@ -2181,7 +2152,13 @@ class AlbumWindow(FocusFrame): elif event.button == 2 and event.type == Gdk.EventType.BUTTON_RELEASE: self._path_to_playlist(path, "append") elif event.button == 3 and event.type == Gdk.EventType.BUTTON_RELEASE: - self._open_album_dialog(path) + album=self._store[path][4] + year=self._store[path][5] + artist=self._store[path][6] + v=self._scroll_vadj.get_value() + h=self._scroll_hadj.get_value() + pop=AlbumPopover(self._client, self._settings, album, artist, year, widget, int(event.x-h), int(event.y-v)) + pop.popup() def _on_key_press_event(self, widget, event): self.handler_block(self._key_press_event) @@ -2196,7 +2173,14 @@ class AlbumWindow(FocusFrame): elif event.keyval == Gdk.keyval_from_name("Menu"): paths=self._iconview.get_selected_items() if len(paths) != 0: - self._open_album_dialog(paths[0]) + album=self._store[paths[0]][4] + year=self._store[paths[0]][5] + artist=self._store[paths[0]][6] + rect=self._iconview.get_cell_rect(paths[0], None)[1] + x=rect.x+rect.width//2 + y=rect.y+rect.height//2 + pop=AlbumPopover(self._client, self._settings, album, artist, year, widget, x, y) + pop.popup() self.handler_unblock(self._key_press_event) def _on_item_activated(self, widget, path): @@ -2247,7 +2231,7 @@ class Browser(Gtk.Paned): self._genre_select=GenreSelect(self._client) self._artist_window=ArtistWindow(self._client, self._settings, self._genre_select) self._search_window=SearchWindow(self._client) - self._album_window=AlbumWindow(self._client, self._settings, self._artist_window, window) + self._album_window=AlbumWindow(self._client, self._settings, self._artist_window) # connect self.back_to_current_album_button.connect("clicked", self._back_to_current_album) @@ -2503,13 +2487,12 @@ class AudioType(Gtk.Label): self.clear() class CoverEventBox(Gtk.EventBox): - def __init__(self, client, settings, window): + def __init__(self, client, settings): super().__init__() # adding vars self._client=client self._settings=settings - self._window=window # connect self._button_press_event=self.connect("button-press-event", self._on_button_press_event) @@ -2530,9 +2513,8 @@ class CoverEventBox(Gtk.EventBox): elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS: self._client.wrapped_call("album_to_playlist", album, artist, album_year, "append") elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: - album_dialog=AlbumDialog(self._window, self._client, self._settings, album, artist, album_year) - album_dialog.open() - album_dialog.destroy() + pop=AlbumPopover(self._client, self._settings, album, artist, album_year, widget, int(event.x), int(event.y)) + pop.popup() def _on_mini_player(self, obj, typestring): if obj.get_property("mini-player"): @@ -2890,11 +2872,10 @@ class CoverPlaylistWindow(Gtk.Paned): # adding vars self._client=client self._settings=settings - self._window=window # cover main_cover=MainCover(self._client, self._settings) - self._cover_event_box=CoverEventBox(self._client, self._settings, self._window) + self._cover_event_box=CoverEventBox(self._client, self._settings) # playlist self._playlist_window=PlaylistWindow(self._client, self._settings)