mirror of
https://github.com/SoongNoonien/mpdevil.git
synced 2023-08-10 21:12:44 +03:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
70ca48b7cc | ||
![]() |
b407f0466d | ||
![]() |
e6c292f658 | ||
![]() |
ebe73cc4ea | ||
![]() |
9e4068f399 | ||
![]() |
fd822aad94 | ||
![]() |
4257369486 | ||
![]() |
d2fc1aff18 | ||
![]() |
79e7ea5973 | ||
![]() |
33e57b9e7f | ||
![]() |
58a7385186 | ||
![]() |
d60d600df2 | ||
![]() |
090c7c814c | ||
![]() |
b76631e98a | ||
![]() |
2633c841cd | ||
![]() |
e8d19066fb | ||
![]() |
7e7d6c673f | ||
![]() |
2cd18d931f | ||
![]() |
52de223b0f |
22
README.md
22
README.md
@@ -1,21 +1,23 @@
|
||||
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.
|
||||
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 simple music browser which aims to be easy to use. 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 your browser. Mpdevil strongly relies on tags.
|
||||
|
||||

|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- play songs without doubleclicking
|
||||
- search songs in your music library
|
||||
- manage multiple mpd servers
|
||||
- filter by genre
|
||||
- control with media keys
|
||||
- displays covers
|
||||
- sends notifications on title change
|
||||
- fetches lyrics from 'www.letras.mus.br'
|
||||
- display large covers
|
||||
- play songs without doubleclick
|
||||
- lyrics from 'www.letras.mus.br'
|
||||
- MPRIS interface (based on mpDris2)
|
||||
- notifications on title change
|
||||
- basic queue manipulation (move and delete single tracks)
|
||||
- search songs
|
||||
- filter by genre
|
||||
- media keys support
|
||||
- many shortcuts
|
||||
- manage multiple mpd servers
|
||||
|
||||
See: https://github.com/SoongNoonien/mpdevil/wiki/Usage
|
||||
|
||||
@@ -74,5 +76,5 @@ sudo update-desktop-database
|
||||
Translation
|
||||
-----------
|
||||
|
||||
mpdevil is currently available in English, German and Dutch. If you speek 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 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 and Dutch. If you speek 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 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/
|
||||
|
||||
|
580
bin/mpdevil
580
bin/mpdevil
@@ -40,7 +40,7 @@ import dbus.service
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
|
||||
VERSION="0.9.6" # sync with setup.py
|
||||
VERSION="0.9.7" # sync with setup.py
|
||||
COVER_REGEX=r"^\.?(album|cover|folder|front).*\.(gif|jpeg|jpg|png)$"
|
||||
|
||||
|
||||
@@ -653,6 +653,7 @@ class Settings(Gio.Settings):
|
||||
BASE_KEY="org.mpdevil"
|
||||
# temp settings
|
||||
mini_player=GObject.Property(type=bool, default=False)
|
||||
cursor_watch=GObject.Property(type=bool, default=False)
|
||||
def __init__(self):
|
||||
super().__init__(schema=self.BASE_KEY)
|
||||
|
||||
@@ -1287,9 +1288,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):
|
||||
@@ -1317,9 +1318,9 @@ class FocusFrame(Gtk.Overlay):
|
||||
# css
|
||||
style_context=self._frame.get_style_context()
|
||||
provider=Gtk.CssProvider()
|
||||
css=b"""* {border-color: @theme_selected_bg_color; border-width: 2px;}"""
|
||||
css=b"""* {border-color: @theme_selected_bg_color; border-width: 0px; border-top-width: 3px;}"""
|
||||
provider.load_from_data(css)
|
||||
style_context.add_provider(provider, 800)
|
||||
style_context.add_provider(provider, 600)
|
||||
|
||||
self.add_overlay(self._frame)
|
||||
self.set_overlay_pass_through(self._frame, True)
|
||||
@@ -1337,17 +1338,25 @@ class FocusFrame(Gtk.Overlay):
|
||||
self._widget.connect("focus-out-event", lambda *args: self._frame.hide())
|
||||
|
||||
class SongPopover(Gtk.Popover):
|
||||
def __init__(self, song, relative, x, y):
|
||||
def __init__(self, song, relative, x, y, offset=26):
|
||||
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
|
||||
rect.y=y+26
|
||||
rect.width = 1
|
||||
rect.height = 1
|
||||
# Gtk places popovers in treeviews 26px above the given position for no obvious reasons, so I move them 26px
|
||||
# This seems to be related to the width/height of the headers in treeviews
|
||||
rect.y=y+offset
|
||||
rect.width=1
|
||||
rect.height=1
|
||||
self.set_pointing_to(rect)
|
||||
self.set_relative_to(relative)
|
||||
|
||||
# css
|
||||
style_context=self.get_style_context()
|
||||
provider=Gtk.CssProvider()
|
||||
css=b"""* {background-color: @theme_base_color}"""
|
||||
provider.load_from_data(css)
|
||||
style_context.add_provider(provider, 600)
|
||||
|
||||
# treeview
|
||||
# (tag, display-value, tooltip)
|
||||
store=Gtk.ListStore(str, str, str)
|
||||
@@ -1357,7 +1366,7 @@ class SongPopover(Gtk.Popover):
|
||||
|
||||
# columns
|
||||
renderer_text=Gtk.CellRendererText(width_chars=50, ellipsize=Pango.EllipsizeMode.MIDDLE, ellipsize_set=True)
|
||||
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
|
||||
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)
|
||||
treeview.append_column(column_tag)
|
||||
@@ -1378,53 +1387,15 @@ class SongPopover(Gtk.Popover):
|
||||
store.append([tag+":", value, tooltip])
|
||||
|
||||
# packing
|
||||
frame=Gtk.Frame(border_width=3)
|
||||
frame.add(treeview)
|
||||
self.add(frame)
|
||||
scroll=Gtk.ScrolledWindow(border_width=3)
|
||||
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||
window=self.get_toplevel()
|
||||
scroll.set_max_content_height(window.get_size()[1]//2)
|
||||
scroll.set_propagate_natural_height(True)
|
||||
scroll.add(treeview)
|
||||
self.add(scroll)
|
||||
|
||||
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)
|
||||
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
|
||||
self.path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename()
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size)
|
||||
|
||||
###########
|
||||
# browser #
|
||||
###########
|
||||
scroll.show_all()
|
||||
|
||||
class SongsView(Gtk.TreeView):
|
||||
def __init__(self, client, store, file_column_id):
|
||||
@@ -1435,6 +1406,7 @@ class SongsView(Gtk.TreeView):
|
||||
self._client=client
|
||||
self._store=store
|
||||
self._file_column_id=file_column_id
|
||||
self._button_event=(None, None)
|
||||
|
||||
# selection
|
||||
self._selection=self.get_selection()
|
||||
@@ -1442,7 +1414,8 @@ class SongsView(Gtk.TreeView):
|
||||
# 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)
|
||||
self.connect("button-release-event", self._on_button_release_event)
|
||||
self.connect("key-release-event", self._on_key_release_event)
|
||||
|
||||
def clear(self):
|
||||
self._store.clear()
|
||||
@@ -1460,49 +1433,45 @@ class SongsView(Gtk.TreeView):
|
||||
self._client.wrapped_call("files_to_playlist", [self._store[path][self._file_column_id]], "play")
|
||||
|
||||
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.wrapped_call("files_to_playlist", [self._store[path][self._file_column_id]])
|
||||
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.wrapped_call("files_to_playlist", [self._store[path][self._file_column_id]], "append")
|
||||
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]
|
||||
file_name=self._store[path][self._file_column_id]
|
||||
pop=SongPopover(self._client.wrapped_call("get_metadata", file_name), widget, int(event.x), int(event.y))
|
||||
pop.popup()
|
||||
except:
|
||||
pass
|
||||
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if path_re is not None:
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._button_event=(event.button, path_re[0])
|
||||
|
||||
def _on_key_press_event(self, widget, event):
|
||||
self.handler_block(self._key_press_event)
|
||||
if event.keyval == Gdk.keyval_from_name("p"):
|
||||
treeview, treeiter=self._selection.get_selected()
|
||||
if treeiter is not None:
|
||||
def _on_button_release_event(self, widget, event):
|
||||
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if path_re is not None:
|
||||
path=path_re[0]
|
||||
if self._button_event == (event.button, path):
|
||||
if event.button == 1 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self._client.wrapped_call("files_to_playlist", [self._store[path][self._file_column_id]])
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self._client.wrapped_call("files_to_playlist", [self._store[path][self._file_column_id]], "append")
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
song=self._client.wrapped_call("get_metadata", self._store[path][self._file_column_id])
|
||||
if self.get_property("headers-visible"):
|
||||
pop=SongPopover(song, widget, int(event.x), int(event.y))
|
||||
else:
|
||||
pop=SongPopover(song, widget, int(event.x), int(event.y), offset=0)
|
||||
pop.popup()
|
||||
self._button_event=(None, None)
|
||||
|
||||
def _on_key_release_event(self, widget, event):
|
||||
treeview, treeiter=self._selection.get_selected()
|
||||
if treeiter is not None:
|
||||
if event.keyval == Gdk.keyval_from_name("p"):
|
||||
self._client.wrapped_call("files_to_playlist", [self._store.get_value(treeiter, self._file_column_id)])
|
||||
elif event.keyval == Gdk.keyval_from_name("a"):
|
||||
treeview, treeiter=self._selection.get_selected()
|
||||
if treeiter is not None:
|
||||
elif event.keyval == Gdk.keyval_from_name("a"):
|
||||
self._client.wrapped_call("files_to_playlist", [self._store.get_value(treeiter, self._file_column_id)], "append")
|
||||
elif event.keyval == Gdk.keyval_from_name("Menu"):
|
||||
treeview, treeiter=self._selection.get_selected()
|
||||
if treeiter is not None:
|
||||
elif event.keyval == Gdk.keyval_from_name("Menu"):
|
||||
path=self._store.get_path(treeiter)
|
||||
cell=self.get_cell_area(path, None)
|
||||
file_name=self._store[path][self._file_column_id]
|
||||
pop=SongPopover(self._client.wrapped_call("get_metadata", file_name), widget, int(cell.x), int(cell.y))
|
||||
pop=SongPopover(self._client.wrapped_call("get_metadata", file_name), widget, cell.x, cell.y)
|
||||
pop.popup()
|
||||
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
|
||||
@@ -1512,9 +1481,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"))
|
||||
@@ -1539,10 +1508,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)
|
||||
@@ -1555,6 +1527,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")
|
||||
|
||||
@@ -1564,6 +1539,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, widget, 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(widget)
|
||||
|
||||
# adding vars
|
||||
self._client=client
|
||||
songs=self._client.wrapped_call("find", "album", album, "date", year, 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="<b>{}</b>".format(title)
|
||||
else:
|
||||
title_artist="<b>{}</b> - {}".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(4*widget.get_allocated_height()//7)
|
||||
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()
|
||||
songs_view.set_property("headers_visible", False)
|
||||
|
||||
# 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)
|
||||
@@ -1841,7 +1933,6 @@ class ArtistWindow(FocusFrame):
|
||||
except:
|
||||
self._store.append([artist, Pango.Weight.BOOK, "", Pango.Weight.BOOK])
|
||||
self._selection.set_mode(Gtk.SelectionMode.SINGLE)
|
||||
self.emit("artists_changed")
|
||||
|
||||
def _on_row_activated(self, widget, path, view_column):
|
||||
for row in self._store: # reset bold text
|
||||
@@ -1860,112 +1951,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="<b>{}</b>".format(title)
|
||||
else:
|
||||
title_artist="<b>{}</b> - {}".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
|
||||
@@ -1983,12 +1976,14 @@ 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)
|
||||
self._iconview.connect("button-release-event", self._on_button_release_event)
|
||||
self._iconview.connect("button-press-event", self._on_button_press_event)
|
||||
self._key_press_event=self.connect("key-press-event", self._on_key_press_event)
|
||||
self._iconview.connect("button-release-event", self._on_button_release_event)
|
||||
self._iconview.connect("key-release-event", self._on_key_release_event)
|
||||
self._client.emitter.connect("update", self._clear)
|
||||
self._client.emitter.connect("disconnected", self._on_disconnected)
|
||||
self._client.emitter.connect("reconnected", self._on_reconnected)
|
||||
@@ -2050,12 +2045,16 @@ class AlbumWindow(FocusFrame):
|
||||
return False # stop after one run
|
||||
|
||||
def _refresh(self, *args):
|
||||
def callback():
|
||||
GLib.idle_add(self._workaround_clear)
|
||||
try: # self._artist_window could still be empty
|
||||
if self._done:
|
||||
self._done=False
|
||||
self._settings.set_property("cursor-watch", True)
|
||||
GLib.idle_add(self._store.clear)
|
||||
self._iconview.set_model(None)
|
||||
try: # self._artist_window can still be empty (e.g. when client is not connected and cover size gets changed)
|
||||
genre, artists=self._artist_window.get_selected_artists()
|
||||
except:
|
||||
GLib.idle_add(self._done_callback)
|
||||
return
|
||||
# show artist names if all albums are shown
|
||||
if len(artists) > 1:
|
||||
self._iconview.set_markup_column(2)
|
||||
@@ -2088,17 +2087,9 @@ class AlbumWindow(FocusFrame):
|
||||
except MPDBase.ConnectionError:
|
||||
GLib.idle_add(self._done_callback)
|
||||
return
|
||||
# display albums
|
||||
if self._settings.get_boolean("sort-albums-by-year"):
|
||||
albums=sorted(albums, key=lambda k: k["year"])
|
||||
else:
|
||||
albums=sorted(albums, key=lambda k: k["album"])
|
||||
size=self._settings.get_int("album-cover")
|
||||
for i, album in enumerate(albums):
|
||||
if self.stop_flag:
|
||||
break
|
||||
else:
|
||||
cover=Cover(self._settings, album["songs"][0]).get_pixbuf(size)
|
||||
|
||||
def display_albums():
|
||||
for i, album in enumerate(albums):
|
||||
# tooltip
|
||||
length_human_readable=ClientHelper.calc_display_length(album["songs"])
|
||||
discs=album["songs"][-1].get("disc", 1)
|
||||
@@ -2121,17 +2112,27 @@ class AlbumWindow(FocusFrame):
|
||||
display_label=display_label.replace("&", "&")
|
||||
display_label_artist=display_label_artist.replace("&", "&")
|
||||
# add album
|
||||
GLib.idle_add(self._add_row,
|
||||
[cover, display_label, display_label_artist, tooltip, album["album"], album["year"], album["artist"]]
|
||||
self._store.append(
|
||||
[album["cover"], display_label, display_label_artist,
|
||||
tooltip, album["album"], album["year"], album["artist"]]
|
||||
)
|
||||
# execute pending events
|
||||
if i%16 == 0:
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration_do(True)
|
||||
GLib.idle_add(self._done_callback)
|
||||
if self._done:
|
||||
self._done=False
|
||||
callback()
|
||||
self._iconview.set_model(self._store)
|
||||
GLib.idle_add(self._done_callback)
|
||||
return False
|
||||
|
||||
def load_covers():
|
||||
size=self._settings.get_int("album-cover")
|
||||
for album in albums:
|
||||
if self.stop_flag:
|
||||
break
|
||||
album["cover"]=Cover(self._settings, album["songs"][0]).get_pixbuf(size)
|
||||
if self.stop_flag:
|
||||
GLib.idle_add(self._done_callback)
|
||||
else:
|
||||
GLib.idle_add(display_albums)
|
||||
|
||||
cover_thread=threading.Thread(target=load_covers, daemon=True)
|
||||
cover_thread.start()
|
||||
elif not self._refresh in self._pending:
|
||||
self.stop_flag=True
|
||||
self._pending.append(self._refresh)
|
||||
@@ -2142,16 +2143,8 @@ 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._settings.set_property("cursor-watch", False)
|
||||
self.stop_flag=False
|
||||
self._done=True
|
||||
pending=self._pending
|
||||
@@ -2177,23 +2170,31 @@ 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()
|
||||
self._button_event=(None, None)
|
||||
|
||||
def _on_key_press_event(self, widget, event):
|
||||
self.handler_block(self._key_press_event)
|
||||
if event.keyval == Gdk.keyval_from_name("p"):
|
||||
paths=self._iconview.get_selected_items()
|
||||
if len(paths) != 0:
|
||||
def _on_key_release_event(self, widget, event):
|
||||
paths=widget.get_selected_items()
|
||||
if len(paths) != 0:
|
||||
if event.keyval == Gdk.keyval_from_name("p"):
|
||||
self._path_to_playlist(paths[0])
|
||||
elif event.keyval == Gdk.keyval_from_name("a"):
|
||||
paths=self._iconview.get_selected_items()
|
||||
if len(paths) != 0:
|
||||
elif event.keyval == Gdk.keyval_from_name("a"):
|
||||
self._path_to_playlist(paths[0], "append")
|
||||
elif event.keyval == Gdk.keyval_from_name("Menu"):
|
||||
paths=self._iconview.get_selected_items()
|
||||
if len(paths) != 0:
|
||||
self._open_album_dialog(paths[0])
|
||||
self.handler_unblock(self._key_press_event)
|
||||
elif event.keyval == Gdk.keyval_from_name("Menu"):
|
||||
album=self._store[paths[0]][4]
|
||||
year=self._store[paths[0]][5]
|
||||
artist=self._store[paths[0]][6]
|
||||
rect=widget.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()
|
||||
|
||||
def _on_item_activated(self, widget, path):
|
||||
treeiter=self._store.get_iter(path)
|
||||
@@ -2218,7 +2219,7 @@ class AlbumWindow(FocusFrame):
|
||||
class Browser(Gtk.Paned):
|
||||
__gsignals__={"search_focus_changed": (GObject.SignalFlags.RUN_FIRST, None, (bool,))}
|
||||
|
||||
def __init__(self, client, settings, window):
|
||||
def __init__(self, client, settings):
|
||||
super().__init__(orientation=Gtk.Orientation.HORIZONTAL) # paned1
|
||||
|
||||
# adding vars
|
||||
@@ -2243,7 +2244,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)
|
||||
@@ -2499,13 +2500,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)
|
||||
@@ -2526,9 +2526,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"):
|
||||
@@ -2545,7 +2544,7 @@ class MainCover(Gtk.Frame):
|
||||
provider=Gtk.CssProvider()
|
||||
css=b"""* {background-color: @theme_base_color; border-width: 0px}"""
|
||||
provider.load_from_data(css)
|
||||
style_context.add_provider(provider, 800)
|
||||
style_context.add_provider(provider, 600)
|
||||
|
||||
# adding vars
|
||||
self._client=client
|
||||
@@ -2593,6 +2592,7 @@ class PlaylistWindow(Gtk.Box):
|
||||
self._playlist_version=None
|
||||
self._icon_size=self._settings.get_int("icon-size-sec")
|
||||
self._inserted_path=None # needed for drag and drop
|
||||
self._button_event=(None, None)
|
||||
|
||||
# buttons
|
||||
provider=Gtk.CssProvider()
|
||||
@@ -2606,7 +2606,7 @@ class PlaylistWindow(Gtk.Box):
|
||||
)
|
||||
self._back_to_current_song_button.set_can_focus(False)
|
||||
style_context=self._back_to_current_song_button.get_style_context()
|
||||
style_context.add_provider(provider, 800)
|
||||
style_context.add_provider(provider, 600)
|
||||
self._clear_button=Gtk.Button(
|
||||
image=AutoSizedIcon("edit-clear-symbolic", "icon-size-sec", self._settings),
|
||||
tooltip_text=_("Clear playlist"),
|
||||
@@ -2615,12 +2615,12 @@ class PlaylistWindow(Gtk.Box):
|
||||
self._clear_button.set_can_focus(False)
|
||||
style_context=self._clear_button.get_style_context()
|
||||
style_context.add_class("destructive-action")
|
||||
style_context.add_provider(provider, 800)
|
||||
style_context.add_provider(provider, 600)
|
||||
|
||||
# treeview
|
||||
# (track, disc, title, artist, album, duration, date, genre, file, weight)
|
||||
self._store=Gtk.ListStore(str, str, str, str, str, str, str, str, str, Pango.Weight)
|
||||
self._treeview=Gtk.TreeView(model=self._store, activate_on_single_click=True, reorderable=True, search_column=-1)
|
||||
self._treeview=Gtk.TreeView(model=self._store, activate_on_single_click=True, reorderable=True, search_column=2)
|
||||
self._selection=self._treeview.get_selection()
|
||||
|
||||
# columns
|
||||
@@ -2673,8 +2673,9 @@ class PlaylistWindow(Gtk.Box):
|
||||
|
||||
# connect
|
||||
self._treeview.connect("row-activated", self._on_row_activated)
|
||||
self._key_press_event=self._treeview.connect("key-press-event", self._on_key_press_event)
|
||||
self._treeview.connect("button-press-event", self._on_button_press_event)
|
||||
self._treeview.connect("button-release-event", self._on_button_release_event)
|
||||
self._treeview.connect("key-release-event", self._on_key_release_event)
|
||||
self._treeview.connect("drag-begin", lambda *args: self._frame.disable())
|
||||
self._treeview.connect("drag-end", lambda *args: self._frame.enable())
|
||||
self._row_deleted=self._store.connect("row-deleted", self._on_row_deleted)
|
||||
@@ -2750,8 +2751,26 @@ class PlaylistWindow(Gtk.Box):
|
||||
except:
|
||||
self._selection.unselect_all()
|
||||
|
||||
def _on_key_press_event(self, widget, event):
|
||||
self._treeview.handler_block(self._key_press_event)
|
||||
def _on_button_press_event(self, widget, event):
|
||||
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if path_re is not None:
|
||||
if event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._button_event=(event.button, path_re[0])
|
||||
|
||||
def _on_button_release_event(self, widget, event):
|
||||
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if path_re is not None:
|
||||
path=path_re[0]
|
||||
if self._button_event == (event.button, path):
|
||||
if event.button == 2 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
self._store.remove(self._store.get_iter(path))
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_RELEASE:
|
||||
song=self._client.wrapped_call("get_metadata", self._store[path][8])
|
||||
pop=SongPopover(song, widget, int(event.x), int(event.y))
|
||||
pop.popup()
|
||||
self._button_event=(None, None)
|
||||
|
||||
def _on_key_release_event(self, widget, event):
|
||||
if event.keyval == Gdk.keyval_from_name("Delete"):
|
||||
treeview, treeiter=self._selection.get_selected()
|
||||
if treeiter is not None:
|
||||
@@ -2767,22 +2786,6 @@ class PlaylistWindow(Gtk.Box):
|
||||
file_name=self._store[path][8]
|
||||
pop=SongPopover(self._client.wrapped_call("get_metadata", file_name), widget, int(cell.x), int(cell.y))
|
||||
pop.popup()
|
||||
self._treeview.handler_unblock(self._key_press_event)
|
||||
|
||||
def _on_button_press_event(self, widget, event):
|
||||
if 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._store.remove(self._store.get_iter(path))
|
||||
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._client.wrapped_call("get_metadata", self._store[path][8]), widget, int(event.x), int(event.y))
|
||||
pop.popup()
|
||||
except:
|
||||
pass
|
||||
|
||||
def _on_row_deleted(self, model, path): # sync treeview to mpd
|
||||
if self._inserted_path is not None: # move
|
||||
@@ -2880,17 +2883,16 @@ class PlaylistWindow(Gtk.Box):
|
||||
self.set_property("visible", visibility)
|
||||
|
||||
class CoverPlaylistWindow(Gtk.Paned):
|
||||
def __init__(self, client, settings, window):
|
||||
def __init__(self, client, settings):
|
||||
super().__init__()
|
||||
|
||||
# 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)
|
||||
@@ -3076,7 +3078,7 @@ class SeekBar(Gtk.Box):
|
||||
provider=Gtk.CssProvider()
|
||||
css=b"""scale fill { background-color: @theme_selected_bg_color; }"""
|
||||
provider.load_from_data(css)
|
||||
style_context.add_provider(provider, 800)
|
||||
style_context.add_provider(provider, 600)
|
||||
|
||||
# connect
|
||||
self._elapsed_event_box.connect("button-release-event", self._on_elapsed_button_release_event)
|
||||
@@ -3557,8 +3559,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
else:
|
||||
icons={"open-menu-symbolic": AutoSizedIcon("open-menu-symbolic", "icon-size", self._settings)}
|
||||
|
||||
self._browser=Browser(self._client, self._settings, self)
|
||||
self._cover_playlist_window=CoverPlaylistWindow(self._client, self._settings, self)
|
||||
self._browser=Browser(self._client, self._settings)
|
||||
self._cover_playlist_window=CoverPlaylistWindow(self._client, self._settings)
|
||||
playback_control=PlaybackControl(self._client, self._settings)
|
||||
seek_bar=SeekBar(self._client)
|
||||
playback_options=PlaybackOptions(self._client, self._settings)
|
||||
@@ -3599,6 +3601,7 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
self._settings.connect("changed::profiles", self._refresh_profiles_menu)
|
||||
self._settings.connect("changed::active-profile", self._on_active_profile_changed)
|
||||
self._settings.connect_after("notify::mini-player", self._on_mini_player)
|
||||
self._settings.connect_after("notify::cursor-watch", self._on_cursor_watch)
|
||||
self._settings.connect("changed::playlist-right", self._on_playlist_pos_changed)
|
||||
self._client.emitter.connect("current_song_changed", self._on_song_changed)
|
||||
self._client.emitter.connect("disconnected", self._on_disconnected)
|
||||
@@ -3746,6 +3749,13 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
self._tmp_saved_size=None
|
||||
self._tmp_saved_maximized=None
|
||||
|
||||
def _on_cursor_watch(self, obj, typestring):
|
||||
if obj.get_property("cursor-watch"):
|
||||
watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
|
||||
self.get_window().set_cursor(watch_cursor)
|
||||
else:
|
||||
self.get_window().set_cursor(None)
|
||||
|
||||
def _on_playlist_pos_changed(self, *args):
|
||||
if self._settings.get_boolean("playlist-right"):
|
||||
self._cover_playlist_window.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ import DistUtilsExtra.auto
|
||||
|
||||
DistUtilsExtra.auto.setup(
|
||||
name='mpdevil',
|
||||
version='0.9.6', # sync with bin/mpdevil
|
||||
version='0.9.7', # sync with bin/mpdevil
|
||||
author="Martin Wagner",
|
||||
author_email="martin.wagner.dev@gmail.com",
|
||||
description=('A simple music browser for MPD'),
|
||||
|
Reference in New Issue
Block a user