mirror of
https://github.com/SoongNoonien/mpdevil.git
synced 2023-08-10 21:12:44 +03:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f13884d059 | ||
![]() |
600e0d5c84 | ||
![]() |
57ace9ec32 | ||
![]() |
38b2286f09 | ||
![]() |
03ee15854c | ||
![]() |
8ab8e7aa29 | ||
![]() |
8a46bab4da | ||
![]() |
d198c3a9f1 | ||
![]() |
7ad93a0943 | ||
![]() |
dc6b802245 | ||
![]() |
7afc76b4f8 | ||
![]() |
3f400e18b0 | ||
![]() |
f9b632802f | ||
![]() |
b3d96f3e9f | ||
![]() |
5c2cc71fb3 |
@@ -2,7 +2,7 @@ README for mpdevil
|
||||
==================
|
||||
mpdevil is focused on playing your local music directly instead of managing playlists or playing network streams. So it neither supports saving playlists nor restoring them. Therefore mpdevil is mainly a music browser which aims to be easy to use. mpdevil dosen't store any client side database of your music library. Instead all tags and covers get presented to you in real time. So you'll never see any outdated information in your browser. mpdevil strongly relies on tags.
|
||||
|
||||

|
||||

|
||||
|
||||
Features
|
||||
--------
|
||||
|
364
bin/mpdevil.py
364
bin/mpdevil.py
@@ -183,6 +183,37 @@ class MpdEventEmitter(GObject.Object):
|
||||
def do_periodic_signal(self):
|
||||
pass
|
||||
|
||||
class ClientHelper():
|
||||
def song_to_str_dict(song): #converts tags with multiple values to comma separated strings
|
||||
return_song=song
|
||||
for tag, value in return_song.items():
|
||||
if type(value) == list:
|
||||
return_song[tag]=(', '.join(value))
|
||||
return return_song
|
||||
|
||||
def song_to_first_str_dict(song): #extracts the first value of multiple value tags
|
||||
return_song=song
|
||||
for tag, value in return_song.items():
|
||||
if type(value) == list:
|
||||
return_song[tag]=value[0]
|
||||
return return_song
|
||||
|
||||
def extend_song_for_display(song):
|
||||
base_song={"title": _("Unknown Title"), "track": "0", "disc": "", "artist": _("Unknown Artist"), "album": _("Unknown Album"), "duration": "0.0", "date": "", "genre": ""}
|
||||
base_song.update(song)
|
||||
base_song["human_duration"]=str(datetime.timedelta(seconds=int(float(base_song["duration"])))).lstrip("0").lstrip(":")
|
||||
return base_song
|
||||
|
||||
def calc_display_length(songs):
|
||||
length=float(0)
|
||||
for song in songs:
|
||||
try:
|
||||
dura=float(song["duration"])
|
||||
except:
|
||||
dura=0.0
|
||||
length=length+dura
|
||||
return str(datetime.timedelta(seconds=int(length))).lstrip("0").lstrip(":")
|
||||
|
||||
class Client(MPDClient):
|
||||
def __init__(self, settings):
|
||||
MPDClient.__init__(self)
|
||||
@@ -242,36 +273,6 @@ class Client(MPDClient):
|
||||
songs=self.find("album", album, "date", year, self.settings.get_artist_type(), artist)
|
||||
self.files_to_playlist([song['file'] for song in songs], append, force)
|
||||
|
||||
def song_to_str_dict(self, song): #converts tags with multiple values to comma separated strings
|
||||
return_song=song
|
||||
for tag, value in return_song.items():
|
||||
if type(value) == list:
|
||||
return_song[tag]=(', '.join(value))
|
||||
return return_song
|
||||
|
||||
def song_to_first_str_dict(self, song): #extracts the first value of multiple value tags
|
||||
return_song=song
|
||||
for tag, value in return_song.items():
|
||||
if type(value) == list:
|
||||
return_song[tag]=value[0]
|
||||
return return_song
|
||||
|
||||
def extend_song_for_display(self, song):
|
||||
base_song={"title": _("Unknown Title"), "track": "0", "disc": "", "artist": _("Unknown Artist"), "album": _("Unknown Album"), "duration": "0.0", "date": "", "genre": ""}
|
||||
base_song.update(song)
|
||||
base_song["human_duration"]=str(datetime.timedelta(seconds=int(float(base_song["duration"])))).lstrip("0").lstrip(":")
|
||||
return base_song
|
||||
|
||||
def calc_display_length(self, songs):
|
||||
length=float(0)
|
||||
for song in songs:
|
||||
try:
|
||||
dura=float(song["duration"])
|
||||
except:
|
||||
dura=0.0
|
||||
length=length+dura
|
||||
return str(datetime.timedelta(seconds=int(length))).lstrip("0").lstrip(":")
|
||||
|
||||
def comp_list(self, *args): #simulates listing behavior of python-mpd2 1.0
|
||||
if "group" in args:
|
||||
raise ValueError("'group' is not supported")
|
||||
@@ -937,6 +938,7 @@ class SongPopover(Gtk.Popover):
|
||||
#packing
|
||||
self.add(frame)
|
||||
|
||||
song=ClientHelper.song_to_str_dict(song)
|
||||
for tag, value in song.items():
|
||||
tooltip=value.replace("&", "&")
|
||||
if tag == "time":
|
||||
@@ -951,89 +953,47 @@ class SongPopover(Gtk.Popover):
|
||||
# self.treeview.queue_resize()
|
||||
|
||||
class SongsView(Gtk.TreeView):
|
||||
def __init__(self, client, show_album=True, sort_enable=True):
|
||||
def __init__(self, client, store, file_column_id):
|
||||
Gtk.TreeView.__init__(self)
|
||||
self.set_model(store)
|
||||
self.set_search_column(-1)
|
||||
self.columns_autosize()
|
||||
|
||||
#add vars
|
||||
self.client=client
|
||||
self.songs=[]
|
||||
|
||||
#store
|
||||
#(track, title, artist, album, duration, file)
|
||||
self.store=Gtk.ListStore(int, str, str, str, str, str)
|
||||
self.set_model(self.store)
|
||||
self.store=store
|
||||
self.file_column_id=file_column_id
|
||||
|
||||
#selection
|
||||
self.selection=self.get_selection()
|
||||
self.selection.set_mode(Gtk.SelectionMode.SINGLE)
|
||||
|
||||
#columns
|
||||
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
|
||||
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
|
||||
|
||||
self.column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0)
|
||||
self.column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_track.set_property("resizable", False)
|
||||
self.append_column(self.column_track)
|
||||
|
||||
self.column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, text=1)
|
||||
self.column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_title.set_property("resizable", False)
|
||||
self.column_title.set_property("expand", True)
|
||||
self.append_column(self.column_title)
|
||||
|
||||
self.column_artist=Gtk.TreeViewColumn(_("Artist"), renderer_text, text=2)
|
||||
self.column_artist.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_artist.set_property("resizable", False)
|
||||
self.column_artist.set_property("expand", True)
|
||||
self.append_column(self.column_artist)
|
||||
|
||||
self.column_album=Gtk.TreeViewColumn(_("Album"), renderer_text, text=3)
|
||||
self.column_album.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_album.set_property("resizable", False)
|
||||
self.column_album.set_property("expand", True)
|
||||
if show_album:
|
||||
self.append_column(self.column_album)
|
||||
|
||||
self.column_time=Gtk.TreeViewColumn(_("Length"), renderer_text, text=4)
|
||||
self.column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_time.set_property("resizable", False)
|
||||
self.append_column(self.column_time)
|
||||
|
||||
if sort_enable:
|
||||
self.column_track.set_sort_column_id(0)
|
||||
self.column_title.set_sort_column_id(1)
|
||||
self.column_artist.set_sort_column_id(2)
|
||||
self.column_album.set_sort_column_id(3)
|
||||
self.column_time.set_sort_column_id(4)
|
||||
|
||||
#connect
|
||||
self.connect("row-activated", self.on_row_activated)
|
||||
self.connect("button-press-event", self.on_button_press_event)
|
||||
self.key_press_event=self.connect("key-press-event", self.on_key_press_event)
|
||||
|
||||
def on_row_activated(self, widget, path, view_column):
|
||||
self.client.files_to_playlist([self.store[path][5]], False, True)
|
||||
self.client.files_to_playlist([self.store[path][self.file_column_id]], False, True)
|
||||
|
||||
def on_button_press_event(self, widget, event):
|
||||
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
try:
|
||||
path=widget.get_path_at_pos(int(event.x), int(event.y))[0]
|
||||
self.client.files_to_playlist([self.store[path][5]], False)
|
||||
self.client.files_to_playlist([self.store[path][self.file_column_id]], False)
|
||||
except:
|
||||
pass
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
try:
|
||||
path=widget.get_path_at_pos(int(event.x), int(event.y))[0]
|
||||
self.client.files_to_playlist([self.store[path][5]], True)
|
||||
self.client.files_to_playlist([self.store[path][self.file_column_id]], True)
|
||||
except:
|
||||
pass
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
try:
|
||||
path=widget.get_path_at_pos(int(event.x), int(event.y))[0]
|
||||
pop=SongPopover(self.songs[int(str(path))], widget, int(event.x), int(event.y))
|
||||
file_name=self.store[path][self.file_column_id]
|
||||
pop=SongPopover(self.client.lsinfo(file_name)[0], widget, int(event.x), int(event.y))
|
||||
pop.popup()
|
||||
pop.show_all()
|
||||
except:
|
||||
@@ -1044,22 +1004,15 @@ class SongsView(Gtk.TreeView):
|
||||
if event.keyval == 112: #p
|
||||
treeview, treeiter=self.selection.get_selected()
|
||||
if not treeiter == None:
|
||||
self.client.files_to_playlist([self.store.get_value(treeiter, 5)], False)
|
||||
self.client.files_to_playlist([self.store.get_value(treeiter, self.file_column_id)], False)
|
||||
elif event.keyval == 97: #a
|
||||
treeview, treeiter=self.selection.get_selected()
|
||||
if not treeiter == None:
|
||||
self.client.files_to_playlist([self.store.get_value(treeiter, 5)], True)
|
||||
self.client.files_to_playlist([self.store.get_value(treeiter, self.file_column_id)], True)
|
||||
# elif event.keyval == 65383: #menu key
|
||||
self.handler_unblock(self.key_press_event)
|
||||
|
||||
def populate(self, songs):
|
||||
self.songs=songs
|
||||
for s in songs:
|
||||
song=self.client.extend_song_for_display(self.client.song_to_str_dict(s))
|
||||
self.store.append([int(song["track"]), song["title"], song["artist"], song["album"], song["human_duration"], song["file"]])
|
||||
|
||||
def clear(self):
|
||||
self.songs=[]
|
||||
self.store.clear()
|
||||
|
||||
def count(self):
|
||||
@@ -1068,7 +1021,7 @@ class SongsView(Gtk.TreeView):
|
||||
def get_files(self):
|
||||
return_list=[]
|
||||
for row in self.store:
|
||||
return_list.append(row[5])
|
||||
return_list.append(row[self.file_column_id])
|
||||
return return_list
|
||||
|
||||
class AlbumDialog(Gtk.Dialog):
|
||||
@@ -1076,6 +1029,16 @@ class AlbumDialog(Gtk.Dialog):
|
||||
Gtk.Dialog.__init__(self, transient_for=parent)
|
||||
self.add_buttons(Gtk.STOCK_ADD, Gtk.ResponseType.ACCEPT, Gtk.STOCK_MEDIA_PLAY, Gtk.ResponseType.YES, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
||||
|
||||
#metadata
|
||||
self.album=album
|
||||
self.artist=artist
|
||||
self.year=year
|
||||
|
||||
#adding vars
|
||||
self.client=client
|
||||
self.settings=settings
|
||||
songs=self.client.find("album", self.album, "date", self.year, self.settings.get_artist_type(), self.artist)
|
||||
|
||||
#determine size
|
||||
size=parent.get_size()
|
||||
diagonal=(size[0]**2+size[1]**2)**(0.5)
|
||||
@@ -1084,23 +1047,58 @@ class AlbumDialog(Gtk.Dialog):
|
||||
self.set_default_size(w, h)
|
||||
|
||||
#title
|
||||
album_duration=ClientHelper.calc_display_length(songs)
|
||||
if year == "":
|
||||
self.set_title(artist+" - "+album)
|
||||
self.set_title(artist+" - "+album+" ("+album_duration+")")
|
||||
else:
|
||||
self.set_title(artist+" - "+album+" ("+year+")")
|
||||
self.set_title(artist+" - "+album+" ("+year+") ("+album_duration+")")
|
||||
|
||||
#adding vars
|
||||
self.client=client
|
||||
self.settings=settings
|
||||
|
||||
#metadata
|
||||
self.album=album
|
||||
self.artist=artist
|
||||
self.year=year
|
||||
#store
|
||||
#(track, title (artist), duration, file)
|
||||
self.store=Gtk.ListStore(int, str, str, str)
|
||||
|
||||
#songs view
|
||||
self.songs_view=SongsView(self.client, False, False)
|
||||
self.songs_view.populate(self.client.find("album", self.album, "date", self.year, self.settings.get_artist_type(), self.artist))
|
||||
self.songs_view=SongsView(self.client, self.store, 3)
|
||||
for s in songs:
|
||||
song=ClientHelper.extend_song_for_display(s)
|
||||
if type(song["title"]) == list: # could be impossible
|
||||
title=(', '.join(song["title"]))
|
||||
else:
|
||||
title=song["title"]
|
||||
if type(song["artist"]) == list:
|
||||
try:
|
||||
song["artist"].remove(self.artist)
|
||||
except:
|
||||
pass
|
||||
artist=(', '.join(song["artist"]))
|
||||
else:
|
||||
artist=song["artist"]
|
||||
if artist != self.artist:
|
||||
title_artist="<b>"+title+"</b> - "+artist
|
||||
else:
|
||||
title_artist="<b>"+title+"</b>"
|
||||
title_artist=title_artist.replace("&", "&")
|
||||
self.store.append([int(song["track"]), title_artist, song["human_duration"], song["file"]])
|
||||
|
||||
#columns
|
||||
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
|
||||
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
|
||||
|
||||
self.column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0)
|
||||
self.column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_track.set_property("resizable", False)
|
||||
self.songs_view.append_column(self.column_track)
|
||||
|
||||
self.column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, markup=1)
|
||||
self.column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_title.set_property("resizable", False)
|
||||
self.column_title.set_property("expand", True)
|
||||
self.songs_view.append_column(self.column_title)
|
||||
|
||||
self.column_time=Gtk.TreeViewColumn(_("Length"), renderer_text, text=2)
|
||||
self.column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_time.set_property("resizable", False)
|
||||
self.songs_view.append_column(self.column_time)
|
||||
|
||||
#scroll
|
||||
scroll=Gtk.ScrolledWindow()
|
||||
@@ -1282,6 +1280,7 @@ class AlbumIconView(Gtk.IconView):
|
||||
self.genre_select=genre_select
|
||||
self.window=window
|
||||
self.stop_flag=True
|
||||
self.button_event=(None, None)
|
||||
|
||||
#cover, display_label, display_label_artist, tooltip(titles), album, year, artist
|
||||
self.store=Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str, str, str, str)
|
||||
@@ -1296,6 +1295,7 @@ class AlbumIconView(Gtk.IconView):
|
||||
|
||||
#connect
|
||||
self.connect("item-activated", self.on_item_activated)
|
||||
self.connect("button-release-event", self.on_button_release_event)
|
||||
self.connect("button-press-event", self.on_button_press_event)
|
||||
self.key_press_event=self.connect("key-press-event", self.on_key_press_event)
|
||||
self.settings.connect("changed::show-album-view-tooltips", self.tooltip_settings)
|
||||
@@ -1366,7 +1366,7 @@ class AlbumIconView(Gtk.IconView):
|
||||
if not self.stop_flag:
|
||||
cover=Cover(lib_path=music_lib, song_file=album["songs"][0]["file"])
|
||||
#tooltip
|
||||
length_human_readable=self.client.calc_display_length(album["songs"])
|
||||
length_human_readable=ClientHelper.calc_display_length(album["songs"])
|
||||
try:
|
||||
discs=int(album["songs"][-1]["disc"])
|
||||
except:
|
||||
@@ -1390,7 +1390,7 @@ class AlbumIconView(Gtk.IconView):
|
||||
GLib.idle_add(self.emit, "done")
|
||||
|
||||
def scroll_to_selected_album(self):
|
||||
song=self.client.song_to_first_str_dict(self.client.currentsong())
|
||||
song=ClientHelper.song_to_first_str_dict(self.client.currentsong())
|
||||
self.unselect_all()
|
||||
row_num=len(self.store)
|
||||
for i in range(0, row_num):
|
||||
@@ -1418,13 +1418,19 @@ class AlbumIconView(Gtk.IconView):
|
||||
album_dialog.destroy()
|
||||
|
||||
def on_button_press_event(self, widget, event):
|
||||
path=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self.button_event=(event.button, path)
|
||||
|
||||
def on_button_release_event(self, widget, event):
|
||||
path=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if not path == None:
|
||||
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
if self.button_event == (event.button, path):
|
||||
if event.button == 1 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self.path_to_playlist(path, False)
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self.path_to_playlist(path, True)
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self.open_album_dialog(path)
|
||||
|
||||
def on_key_press_event(self, widget, event):
|
||||
@@ -1570,7 +1576,7 @@ class MainCover(Gtk.Frame):
|
||||
|
||||
def on_button_press_event(self, widget, event):
|
||||
if self.client.connected():
|
||||
song=self.client.song_to_first_str_dict(self.client.currentsong())
|
||||
song=ClientHelper.song_to_first_str_dict(self.client.currentsong())
|
||||
if not song == {}:
|
||||
try:
|
||||
artist=song[self.settings.get_artist_type()]
|
||||
@@ -1729,7 +1735,7 @@ class PlaylistView(Gtk.Box):
|
||||
def refresh_playlist_info(self):
|
||||
songs=self.client.playlistinfo()
|
||||
if not songs == []:
|
||||
whole_length_human_readable=self.client.calc_display_length(songs)
|
||||
whole_length_human_readable=ClientHelper.calc_display_length(songs)
|
||||
self.playlist_info.set_text(_("%(total_tracks)i titles (%(total_length)s)") % {"total_tracks": len(songs), "total_length": whole_length_human_readable})
|
||||
else:
|
||||
self.playlist_info.set_text("")
|
||||
@@ -1796,7 +1802,7 @@ class PlaylistView(Gtk.Box):
|
||||
if not songs == []:
|
||||
self.playlist_info.set_text("")
|
||||
for s in songs:
|
||||
song=self.client.extend_song_for_display(self.client.song_to_str_dict(s))
|
||||
song=ClientHelper.extend_song_for_display(ClientHelper.song_to_str_dict(s))
|
||||
try:
|
||||
treeiter=self.store.get_iter(song["pos"])
|
||||
self.store.set(treeiter, 0, song["track"], 1, song["disc"], 2, song["title"], 3, song["artist"], 4, song["album"], 5, song["human_duration"], 6, song["date"], 7, song["genre"], 8, song["file"], 9, Pango.Weight.BOOK)
|
||||
@@ -1824,7 +1830,6 @@ class CoverLyricsOSD(Gtk.Overlay):
|
||||
self.client=client
|
||||
self.settings=settings
|
||||
self.window=window
|
||||
self.hide_timeout_id=None
|
||||
|
||||
#cover
|
||||
self.cover=MainCover(self.client, self.settings, self.window)
|
||||
@@ -1832,71 +1837,50 @@ class CoverLyricsOSD(Gtk.Overlay):
|
||||
|
||||
#lyrics button
|
||||
self.lyrics_button=Gtk.Button(image=Gtk.Image.new_from_icon_name("media-view-subtitles-symbolic", Gtk.IconSize.BUTTON))
|
||||
self.lyrics_button.set_label(_("Show lyrics"))
|
||||
self.lyrics_button.set_margin_top(12)
|
||||
self.lyrics_button.set_tooltip_text(_("Show lyrics"))
|
||||
style_context=self.lyrics_button.get_style_context()
|
||||
style_context.add_class("osd")
|
||||
style_context.add_class("circular")
|
||||
|
||||
#revealer
|
||||
self.revealer=Gtk.Revealer()
|
||||
self.revealer.set_halign(3)
|
||||
self.revealer.set_valign(1)
|
||||
self.revealer.add(self.lyrics_button)
|
||||
#workaround to get tooltips in overlay
|
||||
revealer=Gtk.Revealer()
|
||||
revealer.set_halign(2)
|
||||
revealer.set_valign(1)
|
||||
revealer.set_margin_top(6)
|
||||
revealer.set_margin_end(6)
|
||||
revealer.add(self.lyrics_button)
|
||||
revealer.set_reveal_child(True)
|
||||
|
||||
#event box
|
||||
self.event_box=Gtk.EventBox()
|
||||
self.event_box.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
|
||||
self.event_box.add(self.cover)
|
||||
|
||||
#packing
|
||||
self.add(self.event_box)
|
||||
self.add_overlay(self.revealer)
|
||||
self.add_overlay(revealer)
|
||||
|
||||
#connect
|
||||
self.lyrics_button.connect("clicked", self.on_lyrics_clicked)
|
||||
self.motion_notify_event=self.event_box.connect("motion-notify-event", self.on_motion_notify_event)
|
||||
self.lyrics_button.connect("enter-notify-event", self.on_enter_notify_event)
|
||||
self.lyrics_button.connect("leave-notify-event", self.on_leave_notify_event)
|
||||
self.client.emitter.connect("disconnected", self.on_disconnected)
|
||||
self.client.emitter.connect("reconnected", self.on_reconnected)
|
||||
self.event_box.handler_block(self.motion_notify_event)
|
||||
|
||||
def on_reconnected(self, *args):
|
||||
self.event_box.handler_unblock(self.motion_notify_event)
|
||||
self.lyrics_button.set_sensitive(True)
|
||||
|
||||
def on_disconnected(self, *args):
|
||||
self.lyrics_button.set_sensitive(False)
|
||||
self.cover.clear()
|
||||
self.event_box.handler_block(self.motion_notify_event)
|
||||
self.hide_lyrics_button()
|
||||
try:
|
||||
self.lyrics_win.destroy()
|
||||
except:
|
||||
pass
|
||||
|
||||
def hide_lyrics_button(self, *args):
|
||||
self.revealer.set_reveal_child(False)
|
||||
self.hide_timeout_id=None
|
||||
return False
|
||||
|
||||
def on_motion_notify_event(self, *args):
|
||||
self.revealer.set_reveal_child(True)
|
||||
if not self.hide_timeout_id == None:
|
||||
GLib.source_remove(self.hide_timeout_id)
|
||||
self.hide_timeout_id=GLib.timeout_add(1000, self.hide_lyrics_button)
|
||||
|
||||
def on_enter_notify_event(self, *args):
|
||||
if not self.hide_timeout_id == None:
|
||||
GLib.source_remove(self.hide_timeout_id)
|
||||
self.hide_timeout_id=None
|
||||
|
||||
def on_leave_notify_event(self, *args):
|
||||
if not self.hide_timeout_id == None:
|
||||
GLib.source_remove(self.hide_timeout_id)
|
||||
self.hide_timeout_id=GLib.timeout_add(1000, self.hide_lyrics_button)
|
||||
|
||||
def on_lyrics_clicked(self, widget):
|
||||
self.hide_lyrics_button()
|
||||
self.lyrics_button.set_sensitive(False)
|
||||
self.lyrics_win=LyricsWindow(self.client, self.settings)
|
||||
def on_destroy(*args):
|
||||
self.lyrics_button.set_sensitive(True)
|
||||
self.lyrics_win.connect("destroy", on_destroy)
|
||||
self.add_overlay(self.lyrics_win)
|
||||
|
||||
class Browser(Gtk.Box):
|
||||
@@ -1995,7 +1979,7 @@ class Browser(Gtk.Box):
|
||||
|
||||
def back_to_album(self, *args):
|
||||
try: #since this can still be running when the connection is lost, various exceptions can occur
|
||||
song=self.client.song_to_first_str_dict(self.client.currentsong())
|
||||
song=ClientHelper.song_to_first_str_dict(self.client.currentsong())
|
||||
try:
|
||||
artist=song[self.settings.get_artist_type()]
|
||||
except:
|
||||
@@ -2021,6 +2005,7 @@ class Browser(Gtk.Box):
|
||||
self.artist_view.highlight_selected()
|
||||
break
|
||||
else:
|
||||
self.search_button.set_active(False)
|
||||
self.artist_view.treeview.set_cursor(Gtk.TreePath(0), None, False) #set cursor to 'all artists'
|
||||
self.album_view.scroll_to_selected_album()
|
||||
except:
|
||||
@@ -2953,18 +2938,61 @@ class SearchWindow(Gtk.Box):
|
||||
#adding vars
|
||||
self.client=client
|
||||
|
||||
#tag switcher
|
||||
self.tags=Gtk.ComboBoxText()
|
||||
|
||||
#search entry
|
||||
self.search_entry=Gtk.SearchEntry()
|
||||
self.search_entry.set_margin_end(6)
|
||||
self.search_entry.set_margin_start(6)
|
||||
|
||||
#label
|
||||
self.label=Gtk.Label()
|
||||
self.label.set_xalign(1)
|
||||
self.label.set_margin_end(6)
|
||||
|
||||
#store
|
||||
#(track, title, artist, album, duration, file)
|
||||
self.store=Gtk.ListStore(int, str, str, str, str, str)
|
||||
|
||||
#songs view
|
||||
self.songs_view=SongsView(self.client)
|
||||
self.songs_view=SongsView(self.client, self.store, 5)
|
||||
|
||||
#columns
|
||||
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
|
||||
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
|
||||
|
||||
self.column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0)
|
||||
self.column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_track.set_property("resizable", False)
|
||||
self.songs_view.append_column(self.column_track)
|
||||
|
||||
self.column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, text=1)
|
||||
self.column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_title.set_property("resizable", False)
|
||||
self.column_title.set_property("expand", True)
|
||||
self.songs_view.append_column(self.column_title)
|
||||
|
||||
self.column_artist=Gtk.TreeViewColumn(_("Artist"), renderer_text, text=2)
|
||||
self.column_artist.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_artist.set_property("resizable", False)
|
||||
self.column_artist.set_property("expand", True)
|
||||
self.songs_view.append_column(self.column_artist)
|
||||
|
||||
self.column_album=Gtk.TreeViewColumn(_("Album"), renderer_text, text=3)
|
||||
self.column_album.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_album.set_property("resizable", False)
|
||||
self.column_album.set_property("expand", True)
|
||||
self.songs_view.append_column(self.column_album)
|
||||
|
||||
self.column_time=Gtk.TreeViewColumn(_("Length"), renderer_text, text=4)
|
||||
self.column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
self.column_time.set_property("resizable", False)
|
||||
self.songs_view.append_column(self.column_time)
|
||||
|
||||
self.column_track.set_sort_column_id(0)
|
||||
self.column_title.set_sort_column_id(1)
|
||||
self.column_artist.set_sort_column_id(2)
|
||||
self.column_album.set_sort_column_id(3)
|
||||
self.column_time.set_sort_column_id(4)
|
||||
|
||||
#scroll
|
||||
scroll=Gtk.ScrolledWindow()
|
||||
@@ -2984,11 +3012,17 @@ class SearchWindow(Gtk.Box):
|
||||
|
||||
#connect
|
||||
self.search_entry.connect("search-changed", self.on_search_changed)
|
||||
self.tags.connect("changed", self.on_search_changed)
|
||||
self.add_button.connect("clicked", self.on_add_clicked)
|
||||
self.play_button.connect("clicked", self.on_play_clicked)
|
||||
self.open_button.connect("clicked", self.on_open_clicked)
|
||||
self.client.emitter.connect("reconnected", self.on_reconnected)
|
||||
|
||||
#packing
|
||||
vbox=Gtk.Box(spacing=6)
|
||||
vbox.set_property("border-width", 6)
|
||||
vbox.pack_start(self.search_entry, True, True, 0)
|
||||
vbox.pack_end(self.tags, False, False, 0)
|
||||
frame=FocusFrame()
|
||||
frame.set_widget(self.songs_view)
|
||||
frame.add(scroll)
|
||||
@@ -3000,7 +3034,7 @@ class SearchWindow(Gtk.Box):
|
||||
hbox=Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
hbox.pack_start(ButtonBox, 0, False, False)
|
||||
hbox.pack_end(self.label, 0, False, False)
|
||||
self.pack_start(self.search_entry, False, False, 6)
|
||||
self.pack_start(vbox, False, False, 0)
|
||||
self.pack_start(Gtk.Separator.new(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
|
||||
self.pack_start(frame, True, True, 0)
|
||||
self.pack_start(Gtk.Separator.new(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
|
||||
@@ -3015,12 +3049,23 @@ class SearchWindow(Gtk.Box):
|
||||
def clear(self, *args):
|
||||
self.songs_view.clear()
|
||||
self.search_entry.set_text("")
|
||||
self.tags.remove_all()
|
||||
|
||||
def on_reconnected(self, *args):
|
||||
self.tags.append_text("any")
|
||||
for tag in self.client.tagtypes():
|
||||
if not tag.startswith("MUSICBRAINZ"):
|
||||
self.tags.append_text(tag)
|
||||
self.tags.set_active(0)
|
||||
|
||||
def on_search_changed(self, widget):
|
||||
self.songs_view.clear()
|
||||
self.label.set_text("")
|
||||
if len(self.search_entry.get_text()) > 1:
|
||||
self.songs_view.populate(self.client.search("any", self.search_entry.get_text()))
|
||||
songs=self.client.search(self.tags.get_active_text(), self.search_entry.get_text())
|
||||
for s in songs:
|
||||
song=ClientHelper.extend_song_for_display(ClientHelper.song_to_str_dict(s))
|
||||
self.store.append([int(song["track"]), song["title"], song["artist"], song["album"], song["human_duration"], song["file"]])
|
||||
self.label.set_text(_("hits: %i") % (self.songs_view.count()))
|
||||
if self.songs_view.count() == 0:
|
||||
self.add_button.set_sensitive(False)
|
||||
@@ -3064,7 +3109,8 @@ class LyricsWindow(Gtk.Overlay):
|
||||
self.scroll.add(self.text_view)
|
||||
|
||||
#frame
|
||||
frame=Gtk.Frame()
|
||||
frame=FocusFrame()
|
||||
frame.set_widget(self.text_view)
|
||||
style_context=frame.get_style_context()
|
||||
provider=Gtk.CssProvider()
|
||||
css=b"""* {border: 0px; background-color: @theme_base_color; opacity: 0.9;}"""
|
||||
@@ -3106,7 +3152,7 @@ class LyricsWindow(Gtk.Overlay):
|
||||
GLib.idle_add(self.text_buffer.set_text, text, -1)
|
||||
|
||||
def refresh(self, *args):
|
||||
update_thread=threading.Thread(target=self.display_lyrics, kwargs={"current_song": self.client.song_to_first_str_dict(self.client.currentsong())}, daemon=True)
|
||||
update_thread=threading.Thread(target=self.display_lyrics, kwargs={"current_song": ClientHelper.song_to_first_str_dict(self.client.currentsong())}, daemon=True)
|
||||
update_thread.start()
|
||||
|
||||
def getLyrics(self, singer, song): #partially copied from PyLyrics 1.1.0
|
||||
@@ -3257,7 +3303,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
song=self.client.currentsong()
|
||||
if song == {}:
|
||||
raise ValueError("Song out of range")
|
||||
song=self.client.extend_song_for_display(self.client.song_to_str_dict(song))
|
||||
song=ClientHelper.extend_song_for_display(ClientHelper.song_to_str_dict(song))
|
||||
if song["date"] != "":
|
||||
date=" ("+song["date"]+")"
|
||||
else:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
dnl -*- Mode: autoconf -*-
|
||||
dnl Process this file with autoconf to produce a configure script.
|
||||
AC_PREREQ([2.68])
|
||||
AC_INIT([mpdevil], [0.8.2])
|
||||
AC_INIT([mpdevil], [0.8.3])
|
||||
AC_CONFIG_SRCDIR([bin/mpdevil.py])
|
||||
AM_INIT_AUTOMAKE
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
@@ -7,17 +7,17 @@
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="i" name="width">
|
||||
<default>1050</default>
|
||||
<default>1000</default>
|
||||
<summary>Default width of window</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="i" name="height">
|
||||
<default>1020</default>
|
||||
<default>990</default>
|
||||
<summary>Default height of window</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="i" name="paned0">
|
||||
<default>350</default>
|
||||
<default>370</default>
|
||||
<summary>Default position of cover/playlist separator</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
@@ -27,12 +27,12 @@
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="i" name="paned2">
|
||||
<default>598</default>
|
||||
<default>572</default>
|
||||
<summary>Default position of paned1/paned0 separator</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="i" name="album-cover">
|
||||
<default>140</default>
|
||||
<default>130</default>
|
||||
<summary>Size of covers in album view</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
@@ -52,7 +52,7 @@
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="show-stop">
|
||||
<default>false</default>
|
||||
<default>true</default>
|
||||
<summary>Show stop button</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
@@ -67,7 +67,7 @@
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="b" name="sort-albums-by-year">
|
||||
<default>false</default>
|
||||
<default>true</default>
|
||||
<summary>Sort albums by year</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
@@ -102,7 +102,7 @@
|
||||
<description></description>
|
||||
</key>
|
||||
<key type="ai" name="column-sizes">
|
||||
<default>[0, 0, 0, 0, 0, 0, 0, 0]</default>
|
||||
<default>[33, 0, 203, 153, 174, 0, 0, 0]</default>
|
||||
<summary>Sizes of columns in playlist</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB |
BIN
screenshots/mainwindow_0.8.3.png
Normal file
BIN
screenshots/mainwindow_0.8.3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1003 KiB |
Reference in New Issue
Block a user