2020-01-19 22:48:49 +03:00
#!/usr/bin/python3
2020-01-11 13:25:15 +03:00
# -*- coding: utf-8 -*-
#
# mpdevil - MPD Client.
# Copyright 2020 Martin Wagner
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3 of the License.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
2020-03-21 00:09:13 +03:00
# MPRIS interface based on 'mpDris2' (master 19.03.2020) by Jean-Philippe Braun <eon@patapon.info>, Mantas Mikulėnas <grawity@gmail.com>
2020-03-22 19:05:51 +03:00
import gi
2020-01-11 13:25:15 +03:00
gi . require_version ( ' Gtk ' , ' 3.0 ' )
2020-03-03 17:59:18 +03:00
gi . require_version ( ' Notify ' , ' 0.7 ' )
from gi . repository import Gtk , Gio , Gdk , GdkPixbuf , Pango , GObject , GLib , Notify
2020-01-11 13:25:15 +03:00
from mpd import MPDClient
import requests #dev-python/requests
2020-03-22 19:05:51 +03:00
from bs4 import BeautifulSoup , Comment
2020-02-25 00:31:39 +03:00
import threading
2020-01-11 13:25:15 +03:00
import locale
import gettext
import datetime
import os
import sys
2020-04-07 19:02:43 +03:00
import re
2020-01-11 13:25:15 +03:00
2020-03-22 19:05:51 +03:00
#MPRIS modules
2020-03-21 00:09:13 +03:00
import dbus
import dbus . service
from dbus . mainloop . glib import DBusGMainLoop
import base64
2020-04-09 01:26:21 +03:00
DATADIR = ' @datadir@ '
NAME = ' mpdevil '
VERSION = ' @version@ '
PACKAGE = NAME . lower ( )
2020-03-22 19:05:51 +03:00
try :
locale . setlocale ( locale . LC_ALL , ' ' )
locale . bindtextdomain ( PACKAGE , ' @datadir@/locale ' )
gettext . bindtextdomain ( PACKAGE , ' @datadir@/locale ' )
gettext . textdomain ( PACKAGE )
gettext . install ( PACKAGE , localedir = ' @datadir@/locale ' )
except locale . Error :
print ( ' cannot use system locale. ' )
locale . setlocale ( locale . LC_ALL , ' C ' )
gettext . textdomain ( PACKAGE )
gettext . install ( PACKAGE , localedir = ' @datadir@/locale ' )
2020-01-11 13:25:15 +03:00
class IntEntry ( Gtk . SpinButton ) :
2020-03-10 15:50:36 +03:00
def __init__ ( self , default , lower , upper , step ) :
2020-01-11 13:25:15 +03:00
Gtk . SpinButton . __init__ ( self )
2020-04-09 01:26:21 +03:00
adj = Gtk . Adjustment ( value = default , lower = lower , upper = upper , step_increment = step )
2020-01-11 13:25:15 +03:00
self . set_adjustment ( adj )
def get_int ( self ) :
return int ( self . get_value ( ) )
def set_int ( self , value ) :
self . set_value ( value )
2020-05-26 19:37:51 +03:00
class FocusFrame ( Gtk . Overlay ) :
2020-03-31 18:36:41 +03:00
def __init__ ( self ) :
2020-05-26 19:37:51 +03:00
Gtk . Overlay . __init__ ( self )
2020-03-28 16:09:06 +03:00
2020-05-26 19:37:51 +03:00
self . frame = Gtk . Frame ( )
self . frame . set_no_show_all ( True )
self . style_context = self . frame . get_style_context ( )
2020-04-09 01:26:21 +03:00
self . provider = Gtk . CssProvider ( )
2020-05-26 19:37:51 +03:00
css = b """ * { border-color: @theme_selected_bg_color; border-width: 2px;} """
2020-03-28 16:09:06 +03:00
self . provider . load_from_data ( css )
2020-05-26 19:37:51 +03:00
self . style_context . add_provider ( self . provider , 800 )
2020-03-28 16:09:06 +03:00
2020-05-26 19:37:51 +03:00
self . add_overlay ( self . frame )
self . set_overlay_pass_through ( self . frame , True )
2020-03-30 18:08:59 +03:00
2020-03-31 18:36:41 +03:00
def set_widget ( self , widget ) :
widget . connect ( " focus-in-event " , self . on_focus_in_event )
widget . connect ( " focus-out-event " , self . on_focus_out_event )
2020-03-28 16:09:06 +03:00
def on_focus_in_event ( self , * args ) :
2020-05-26 19:37:51 +03:00
self . frame . show ( )
2020-03-28 16:09:06 +03:00
def on_focus_out_event ( self , * args ) :
2020-05-26 19:37:51 +03:00
self . frame . hide ( )
2020-03-28 16:09:06 +03:00
2020-01-11 13:25:15 +03:00
class Cover ( object ) :
2020-04-07 18:57:16 +03:00
regex = re . compile ( r ' ^ \ .?(album|cover|folder|front).* \ .(gif|jpeg|jpg|png)$ ' , flags = re . IGNORECASE )
2020-03-22 13:25:04 +03:00
def __init__ ( self , lib_path , song_file ) :
2020-04-07 19:10:42 +03:00
self . lib_path = lib_path or " "
2020-01-11 13:25:15 +03:00
self . path = None
if not song_file == None :
2020-04-07 18:57:16 +03:00
head , tail = os . path . split ( song_file )
song_dir = os . path . join ( self . lib_path , head )
if os . path . exists ( song_dir ) :
for f in os . listdir ( song_dir ) :
if self . regex . match ( f ) :
self . path = os . path . join ( song_dir , f )
break
2020-01-11 13:25:15 +03:00
def get_pixbuf ( self , size ) :
if self . path == None :
2020-04-09 01:26:21 +03:00
self . path = Gtk . IconTheme . get_default ( ) . lookup_icon ( " mpdevil " , size , Gtk . IconLookupFlags . FORCE_SVG ) . get_filename ( ) #fallback cover
2020-01-11 13:25:15 +03:00
return GdkPixbuf . Pixbuf . new_from_file_at_size ( self . path , size , size )
2020-02-25 00:31:39 +03:00
class MpdEventEmitter ( GObject . Object ) :
2020-04-09 01:26:21 +03:00
__gsignals__ = {
2020-03-22 23:18:54 +03:00
' database ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' update ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' stored_playlist ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' playlist ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' player ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' mixer ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' output ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' options ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' sticker ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' subscription ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' message ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' disconnected ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' reconnected ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
2020-05-16 10:42:27 +03:00
' playing_file_changed ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) ) ,
' periodic_signal ' : ( GObject . SignalFlags . RUN_FIRST , None , ( ) )
2020-03-22 17:03:07 +03:00
}
2020-05-16 10:42:27 +03:00
def __init__ ( self ) :
2020-02-25 00:31:39 +03:00
super ( ) . __init__ ( )
2020-03-22 17:03:07 +03:00
#mpd signals
def do_database ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_update ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_stored_playlist ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_playlist ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_player ( self ) :
2020-05-16 10:42:27 +03:00
pass
2020-02-25 00:31:39 +03:00
2020-03-22 17:03:07 +03:00
def do_mixer ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_output ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_options ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_sticker ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_subscription ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
def do_message ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-03-22 17:03:07 +03:00
#custom signals
def do_disconnected ( self ) :
2020-05-16 10:42:27 +03:00
pass
2020-02-25 00:31:39 +03:00
2020-03-22 17:03:07 +03:00
def do_reconnected ( self ) :
2020-05-16 10:42:27 +03:00
pass
2020-03-22 16:25:04 +03:00
2020-03-22 17:03:07 +03:00
def do_playing_file_changed ( self ) :
2020-02-25 00:31:39 +03:00
pass
2020-02-22 17:22:57 +03:00
2020-05-16 10:42:27 +03:00
def do_periodic_signal ( self ) :
pass
2020-06-04 21:36:34 +03:00
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 ( " : " )
2020-05-16 10:42:27 +03:00
class Client ( MPDClient ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , settings ) :
2020-05-16 10:42:27 +03:00
MPDClient . __init__ ( self )
self . settings = settings
self . settings . connect ( " changed::active-profile " , self . on_settings_changed )
#idle client
self . idle_client = MPDClient ( )
2020-03-30 12:54:04 +03:00
#adding vars
self . settings = settings
2020-05-16 10:42:27 +03:00
self . emitter = MpdEventEmitter ( )
2020-03-30 12:54:04 +03:00
2020-05-16 10:42:27 +03:00
self . current_file = None
def start ( self ) :
if self . disconnected_loop ( ) :
self . disconnected_timeout_id = GLib . timeout_add ( 1000 , self . disconnected_loop )
def connected ( self ) :
try :
self . ping ( )
return True
except :
return False
def on_settings_changed ( self , * args ) :
self . disconnect ( )
self . idle_client . disconnect ( )
2020-03-30 12:54:04 +03:00
2020-03-31 00:48:46 +03:00
def files_to_playlist ( self , files , append , force = False ) :
2020-03-30 12:54:04 +03:00
if append :
2020-03-31 00:48:46 +03:00
for f in files :
self . add ( f )
2020-03-30 12:54:04 +03:00
else :
if self . settings . get_boolean ( " force-mode " ) or force or self . status ( ) [ " state " ] == " stop " :
2020-03-31 00:48:46 +03:00
if not files == [ ] :
2020-03-30 12:54:04 +03:00
self . clear ( )
2020-03-31 00:48:46 +03:00
for f in files :
self . add ( f )
2020-03-30 12:54:04 +03:00
self . play ( )
else :
status = self . status ( )
self . moveid ( status [ " songid " ] , 0 )
2020-04-02 22:20:36 +03:00
current_song_file = self . playlistinfo ( ) [ 0 ] [ " file " ]
2020-03-30 12:54:04 +03:00
try :
self . delete ( ( 1 , ) ) # delete all songs, but the first. #bad song index possible
except :
pass
2020-03-31 00:48:46 +03:00
for f in files :
2020-04-02 22:20:36 +03:00
if not f == current_song_file :
2020-03-31 00:48:46 +03:00
self . add ( f )
else :
2020-04-11 10:52:31 +03:00
self . move ( 0 , ( len ( self . playlistinfo ( ) ) - 1 ) )
2020-03-31 00:48:46 +03:00
def album_to_playlist ( self , album , artist , year , append , force = False ) :
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 )
2020-03-30 12:54:04 +03:00
2020-05-03 12:58:35 +03:00
def comp_list ( self , * args ) : #simulates listing behavior of python-mpd2 1.0
if " group " in args :
raise ValueError ( " ' group ' is not supported " )
native_list = self . list ( * args )
if len ( native_list ) > 0 :
if type ( native_list [ 0 ] ) == dict :
return ( [ l [ args [ 0 ] ] for l in native_list ] )
else :
return native_list
else :
return ( [ ] )
2020-05-16 10:42:27 +03:00
def loop ( self , * args ) :
#idle
try :
try :
idle_return = self . idle_client . noidle ( )
for i in idle_return :
self . emitter . emit ( i )
if " player " in idle_return :
current_song = self . idle_client . currentsong ( )
if not current_song == { } :
if not current_song [ ' file ' ] == self . current_file :
self . emitter . emit ( " playing_file_changed " )
self . current_file = current_song [ ' file ' ]
else :
self . emitter . emit ( " playing_file_changed " )
self . current_file = None
except :
pass
self . idle_client . send_idle ( )
#heartbeat
status = self . status ( )
if status [ ' state ' ] == " stop " or status [ ' state ' ] == " pause " :
self . ping ( )
else :
self . emitter . emit ( " periodic_signal " )
except :
try :
self . idle_client . disconnect ( )
except :
pass
try :
self . disconnect ( )
except :
pass
self . emitter . emit ( " disconnected " )
if self . disconnected_loop ( ) :
self . disconnected_timeout_id = GLib . timeout_add ( 1000 , self . disconnected_loop )
return False
return True
def disconnected_loop ( self , * args ) :
self . current_file = None
active = self . settings . get_int ( " active-profile " )
try :
self . connect ( self . settings . get_value ( " hosts " ) [ active ] , self . settings . get_value ( " ports " ) [ active ] )
if self . settings . get_value ( " passwords " ) [ active ] != " " :
self . password ( self . settings . get_value ( " passwords " ) [ active ] )
except :
print ( " connect failed " )
return True
try :
self . idle_client . connect ( self . settings . get_value ( " hosts " ) [ active ] , self . settings . get_value ( " ports " ) [ active ] )
if self . settings . get_value ( " passwords " ) [ active ] != " " :
self . idle_client . password ( self . settings . get_value ( " passwords " ) [ active ] )
except :
print ( " connect failed " )
print ( " max clients could be too small " )
self . diconnect ( )
return True
#connect successful
self . main_timeout_id = GLib . timeout_add ( 100 , self . loop )
2020-05-16 12:18:43 +03:00
self . emitter . emit ( " periodic_signal " )
2020-03-30 12:54:04 +03:00
self . emitter . emit ( " playlist " )
self . emitter . emit ( " player " )
2020-05-16 10:42:27 +03:00
self . emitter . emit ( " playing_file_changed " )
2020-03-30 12:54:04 +03:00
self . emitter . emit ( " options " )
self . emitter . emit ( " mixer " )
self . emitter . emit ( " update " )
2020-05-16 10:42:27 +03:00
self . emitter . emit ( " reconnected " )
return False
2020-03-30 12:54:04 +03:00
2020-03-22 19:05:51 +03:00
class MPRISInterface ( dbus . service . Object ) : #TODO emit Seeked if needed
2020-04-09 01:26:21 +03:00
__introspect_interface = " org.freedesktop.DBus.Introspectable "
__prop_interface = dbus . PROPERTIES_IFACE
2020-03-21 00:09:13 +03:00
2020-04-07 19:02:43 +03:00
# python dbus bindings don't include annotations and properties
2020-04-09 01:26:21 +03:00
MPRIS2_INTROSPECTION = """ <node name= " /org/mpris/MediaPlayer2 " >
2020-04-07 19:02:43 +03:00
< interface name = " org.freedesktop.DBus.Introspectable " >
< method name = " Introspect " >
< arg direction = " out " name = " xml_data " type = " s " / >
< / method >
< / interface >
< interface name = " org.freedesktop.DBus.Properties " >
< method name = " Get " >
< arg direction = " in " name = " interface_name " type = " s " / >
< arg direction = " in " name = " property_name " type = " s " / >
< arg direction = " out " name = " value " type = " v " / >
< / method >
< method name = " GetAll " >
< arg direction = " in " name = " interface_name " type = " s " / >
< arg direction = " out " name = " properties " type = " a {sv} " / >
< / method >
< method name = " Set " >
< arg direction = " in " name = " interface_name " type = " s " / >
< arg direction = " in " name = " property_name " type = " s " / >
< arg direction = " in " name = " value " type = " v " / >
< / method >
< signal name = " PropertiesChanged " >
< arg name = " interface_name " type = " s " / >
< arg name = " changed_properties " type = " a {sv} " / >
< arg name = " invalidated_properties " type = " as " / >
< / signal >
< / interface >
< interface name = " org.mpris.MediaPlayer2 " >
< method name = " Raise " / >
< method name = " Quit " / >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " false " / >
< property name = " CanQuit " type = " b " access = " read " / >
< property name = " CanRaise " type = " b " access = " read " / >
< property name = " HasTrackList " type = " b " access = " read " / >
< property name = " Identity " type = " s " access = " read " / >
< property name = " DesktopEntry " type = " s " access = " read " / >
< property name = " SupportedUriSchemes " type = " as " access = " read " / >
< property name = " SupportedMimeTypes " type = " as " access = " read " / >
< / interface >
< interface name = " org.mpris.MediaPlayer2.Player " >
< method name = " Next " / >
< method name = " Previous " / >
< method name = " Pause " / >
< method name = " PlayPause " / >
< method name = " Stop " / >
< method name = " Play " / >
< method name = " Seek " >
< arg direction = " in " name = " Offset " type = " x " / >
< / method >
< method name = " SetPosition " >
< arg direction = " in " name = " TrackId " type = " o " / >
< arg direction = " in " name = " Position " type = " x " / >
< / method >
< method name = " OpenUri " >
< arg direction = " in " name = " Uri " type = " s " / >
< / method >
< signal name = " Seeked " >
< arg name = " Position " type = " x " / >
< / signal >
< property name = " PlaybackStatus " type = " s " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " LoopStatus " type = " s " access = " readwrite " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " Rate " type = " d " access = " readwrite " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " Shuffle " type = " b " access = " readwrite " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " Metadata " type = " a {sv} " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " Volume " type = " d " access = " readwrite " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " false " / >
< / property >
< property name = " Position " type = " x " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " false " / >
< / property >
< property name = " MinimumRate " type = " d " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " MaximumRate " type = " d " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanGoNext " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanGoPrevious " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanPlay " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanPause " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanSeek " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " true " / >
< / property >
< property name = " CanControl " type = " b " access = " read " >
< annotation name = " org.freedesktop.DBus.Property.EmitsChangedSignal " value = " false " / >
< / property >
< / interface >
< / node > """
# MPRIS allowed metadata tags
2020-04-09 01:26:21 +03:00
allowed_tags = {
2020-04-07 19:02:43 +03:00
' mpris:trackid ' : dbus . ObjectPath ,
' mpris:length ' : dbus . Int64 ,
' mpris:artUrl ' : str ,
' xesam:album ' : str ,
' xesam:albumArtist ' : list ,
' xesam:artist ' : list ,
' xesam:asText ' : str ,
' xesam:audioBPM ' : int ,
' xesam:comment ' : list ,
' xesam:composer ' : list ,
' xesam:contentCreated ' : str ,
' xesam:discNumber ' : int ,
' xesam:firstUsed ' : str ,
' xesam:genre ' : list ,
' xesam:lastUsed ' : str ,
' xesam:lyricist ' : str ,
' xesam:title ' : str ,
' xesam:trackNumber ' : int ,
' xesam:url ' : str ,
' xesam:useCount ' : int ,
' xesam:userRating ' : float ,
}
2020-03-30 12:54:04 +03:00
def __init__ ( self , window , client , settings ) :
2020-03-21 00:09:13 +03:00
dbus . service . Object . __init__ ( self , dbus . SessionBus ( ) , " /org/mpris/MediaPlayer2 " )
2020-04-09 01:26:21 +03:00
self . _name = " org.mpris.MediaPlayer2.mpdevil "
2020-03-21 00:09:13 +03:00
2020-04-09 01:26:21 +03:00
self . _bus = dbus . SessionBus ( )
self . _uname = self . _bus . get_unique_name ( )
self . _dbus_obj = self . _bus . get_object ( " org.freedesktop.DBus " , " /org/freedesktop/DBus " )
2020-03-21 00:09:13 +03:00
self . _dbus_obj . connect_to_signal ( " NameOwnerChanged " , self . _name_owner_changed_callback , arg0 = self . _name )
self . window = window
self . client = client
self . settings = settings
self . metadata = { }
#connect
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " player " , self . on_player_changed )
self . client . emitter . connect ( " playing_file_changed " , self . on_file_changed )
self . client . emitter . connect ( " mixer " , self . on_volume_changed )
self . client . emitter . connect ( " options " , self . on_options_changed )
2020-03-21 00:09:13 +03:00
def on_player_changed ( self , * args ) :
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' PlaybackStatus ' )
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' CanGoNext ' )
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' CanGoPrevious ' )
2020-03-22 16:25:04 +03:00
def on_file_changed ( self , * args ) :
2020-03-21 00:09:13 +03:00
self . update_metadata ( )
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' Metadata ' )
def on_volume_changed ( self , * args ) :
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' Volume ' )
def on_options_changed ( self , * args ) :
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' LoopStatus ' )
self . update_property ( ' org.mpris.MediaPlayer2.Player ' , ' Shuffle ' )
def update_metadata ( self ) : #TODO
"""
Translate metadata returned by MPD to the MPRIS v2 syntax .
http : / / www . freedesktop . org / wiki / Specifications / mpris - spec / metadata
"""
2020-04-09 01:26:21 +03:00
mpd_meta = self . client . currentsong ( )
self . metadata = { }
2020-03-21 00:09:13 +03:00
for tag in ( ' album ' , ' title ' ) :
if tag in mpd_meta :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam: %s ' % tag ] = mpd_meta [ tag ]
2020-03-21 00:09:13 +03:00
if ' id ' in mpd_meta :
2020-04-09 01:26:21 +03:00
self . metadata [ ' mpris:trackid ' ] = " /org/mpris/MediaPlayer2/Track/ %s " % mpd_meta [ ' id ' ]
2020-03-21 00:09:13 +03:00
if ' time ' in mpd_meta :
2020-04-09 01:26:21 +03:00
self . metadata [ ' mpris:length ' ] = int ( mpd_meta [ ' time ' ] ) * 1000000
2020-03-21 00:09:13 +03:00
if ' date ' in mpd_meta :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:contentCreated ' ] = mpd_meta [ ' date ' ] [ 0 : 4 ]
2020-03-21 00:09:13 +03:00
if ' track ' in mpd_meta :
# TODO: Is it even *possible* for mpd_meta['track'] to be a list?
if type ( mpd_meta [ ' track ' ] ) == list and len ( mpd_meta [ ' track ' ] ) > 0 :
2020-04-09 01:26:21 +03:00
track = str ( mpd_meta [ ' track ' ] [ 0 ] )
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
track = str ( mpd_meta [ ' track ' ] )
2020-03-21 00:09:13 +03:00
2020-04-09 01:26:21 +03:00
m = re . match ( ' ^([0-9]+) ' , track )
2020-03-21 00:09:13 +03:00
if m :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:trackNumber ' ] = int ( m . group ( 1 ) )
2020-03-21 00:09:13 +03:00
# Ensure the integer is signed 32bit
if self . metadata [ ' xesam:trackNumber ' ] & 0x80000000 :
self . metadata [ ' xesam:trackNumber ' ] + = - 0x100000000
else :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:trackNumber ' ] = 0
2020-03-21 00:09:13 +03:00
if ' disc ' in mpd_meta :
# TODO: Same as above. When is it a list?
if type ( mpd_meta [ ' disc ' ] ) == list and len ( mpd_meta [ ' disc ' ] ) > 0 :
2020-04-09 01:26:21 +03:00
disc = str ( mpd_meta [ ' disc ' ] [ 0 ] )
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
disc = str ( mpd_meta [ ' disc ' ] )
2020-03-21 00:09:13 +03:00
2020-04-09 01:26:21 +03:00
m = re . match ( ' ^([0-9]+) ' , disc )
2020-03-21 00:09:13 +03:00
if m :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:discNumber ' ] = int ( m . group ( 1 ) )
2020-03-21 00:09:13 +03:00
if ' artist ' in mpd_meta :
if type ( mpd_meta [ ' artist ' ] ) == list :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:artist ' ] = mpd_meta [ ' artist ' ]
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:artist ' ] = [ mpd_meta [ ' artist ' ] ]
2020-03-21 00:09:13 +03:00
if ' composer ' in mpd_meta :
if type ( mpd_meta [ ' composer ' ] ) == list :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:composer ' ] = mpd_meta [ ' composer ' ]
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:composer ' ] = [ mpd_meta [ ' composer ' ] ]
2020-03-21 00:09:13 +03:00
# Stream: populate some missings tags with stream's name
if ' name ' in mpd_meta :
if ' xesam:title ' not in self . metadata :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:title ' ] = mpd_meta [ ' name ' ]
2020-03-21 00:09:13 +03:00
elif ' xesam:album ' not in self . metadata :
2020-04-09 01:26:21 +03:00
self . metadata [ ' xesam:album ' ] = mpd_meta [ ' name ' ]
2020-03-21 00:09:13 +03:00
if ' file ' in mpd_meta :
2020-04-09 01:26:21 +03:00
song_file = mpd_meta [ ' file ' ]
self . metadata [ ' xesam:url ' ] = " file:// " + os . path . join ( self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file )
2020-03-22 13:25:04 +03:00
cover = Cover ( lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = song_file )
2020-03-21 00:09:13 +03:00
if not cover . path == None :
2020-04-09 01:26:21 +03:00
self . metadata [ ' mpris:artUrl ' ] = " file:// " + cover . path
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
self . metadata [ ' mpris:artUrl ' ] = None
2020-03-21 00:09:13 +03:00
# Cast self.metadata to the correct type, or discard it
for key , value in self . metadata . items ( ) :
try :
2020-04-09 01:26:21 +03:00
self . metadata [ key ] = self . allowed_tags [ key ] ( value )
2020-03-21 00:09:13 +03:00
except ValueError :
del self . metadata [ key ]
def _name_owner_changed_callback ( self , name , old_owner , new_owner ) :
if name == self . _name and old_owner == self . _uname and new_owner != " " :
try :
2020-04-09 01:26:21 +03:00
pid = self . _dbus_obj . GetConnectionUnixProcessID ( new_owner )
2020-03-21 00:09:13 +03:00
except :
2020-04-09 01:26:21 +03:00
pid = None
2020-03-21 00:09:13 +03:00
loop . quit ( )
def acquire_name ( self ) :
2020-04-09 01:26:21 +03:00
self . _bus_name = dbus . service . BusName ( self . _name , bus = self . _bus , allow_replacement = True , replace_existing = True )
2020-03-21 00:09:13 +03:00
def release_name ( self ) :
if hasattr ( self , " _bus_name " ) :
del self . _bus_name
2020-04-09 01:26:21 +03:00
__root_interface = " org.mpris.MediaPlayer2 "
__root_props = {
2020-03-21 00:09:13 +03:00
" CanQuit " : ( False , None ) ,
" CanRaise " : ( True , None ) ,
" DesktopEntry " : ( " mpdevil " , None ) ,
" HasTrackList " : ( False , None ) ,
" Identity " : ( " mpdevil " , None ) ,
" SupportedUriSchemes " : ( dbus . Array ( signature = " s " ) , None ) ,
" SupportedMimeTypes " : ( dbus . Array ( signature = " s " ) , None )
}
def __get_playback_status ( self ) :
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
2020-03-21 00:09:13 +03:00
return { ' play ' : ' Playing ' , ' pause ' : ' Paused ' , ' stop ' : ' Stopped ' } [ status [ ' state ' ] ]
def __set_loop_status ( self , value ) :
if value == " Playlist " :
self . client . repeat ( 1 )
self . client . single ( 0 )
elif value == " Track " :
self . client . repeat ( 1 )
self . client . single ( 1 )
elif value == " None " :
self . client . repeat ( 0 )
self . client . single ( 0 )
else :
raise dbus . exceptions . DBusException ( " Loop mode %r not supported " % value )
return
def __get_loop_status ( self ) :
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
2020-03-21 00:09:13 +03:00
if int ( status [ ' repeat ' ] ) == 1 :
if int ( status . get ( ' single ' , 0 ) ) == 1 :
return " Track "
else :
return " Playlist "
else :
return " None "
def __set_shuffle ( self , value ) :
self . client . random ( value )
return
def __get_shuffle ( self ) :
if int ( self . client . status ( ) [ ' random ' ] ) == 1 :
return True
else :
return False
def __get_metadata ( self ) :
return dbus . Dictionary ( self . metadata , signature = ' sv ' )
def __get_volume ( self ) :
2020-04-09 01:26:21 +03:00
vol = float ( self . client . status ( ) . get ( ' volume ' , 0 ) )
2020-03-21 00:09:13 +03:00
if vol > 0 :
return vol / 100.0
else :
return 0.0
def __set_volume ( self , value ) :
if value > = 0 and value < = 1 :
self . client . setvol ( int ( value * 100 ) )
return
def __get_position ( self ) :
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
2020-03-21 00:09:13 +03:00
if ' time ' in status :
2020-04-09 01:26:21 +03:00
current , end = status [ ' time ' ] . split ( ' : ' )
2020-03-21 00:09:13 +03:00
return dbus . Int64 ( ( int ( current ) * 1000000 ) )
else :
return dbus . Int64 ( 0 )
def __get_can_next_prev ( self ) :
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
2020-03-21 00:09:13 +03:00
if status [ ' state ' ] == " stop " :
return False
else :
return True
2020-04-09 01:26:21 +03:00
__player_interface = " org.mpris.MediaPlayer2.Player "
__player_props = {
2020-03-21 00:09:13 +03:00
" PlaybackStatus " : ( __get_playback_status , None ) ,
" LoopStatus " : ( __get_loop_status , __set_loop_status ) ,
" Rate " : ( 1.0 , None ) ,
" Shuffle " : ( __get_shuffle , __set_shuffle ) ,
" Metadata " : ( __get_metadata , None ) ,
" Volume " : ( __get_volume , __set_volume ) ,
" Position " : ( __get_position , None ) ,
" MinimumRate " : ( 1.0 , None ) ,
" MaximumRate " : ( 1.0 , None ) ,
" CanGoNext " : ( __get_can_next_prev , None ) ,
" CanGoPrevious " : ( __get_can_next_prev , None ) ,
" CanPlay " : ( True , None ) ,
" CanPause " : ( True , None ) ,
" CanSeek " : ( True , None ) ,
" CanControl " : ( True , None ) ,
}
2020-04-09 01:26:21 +03:00
__prop_mapping = {
2020-03-21 00:09:13 +03:00
__player_interface : __player_props ,
__root_interface : __root_props ,
}
@dbus.service.method ( __introspect_interface )
def Introspect ( self ) :
2020-04-07 19:02:43 +03:00
return self . MPRIS2_INTROSPECTION
2020-03-21 00:09:13 +03:00
@dbus.service.signal ( __prop_interface , signature = " sa {sv} as " )
def PropertiesChanged ( self , interface , changed_properties , invalidated_properties ) :
pass
@dbus.service.method ( __prop_interface , in_signature = " ss " , out_signature = " v " )
def Get ( self , interface , prop ) :
2020-04-09 01:26:21 +03:00
getter , setter = self . __prop_mapping [ interface ] [ prop ]
2020-03-21 00:09:13 +03:00
if callable ( getter ) :
return getter ( self )
return getter
@dbus.service.method ( __prop_interface , in_signature = " ssv " , out_signature = " " )
def Set ( self , interface , prop , value ) :
2020-04-09 01:26:21 +03:00
getter , setter = self . __prop_mapping [ interface ] [ prop ]
2020-03-21 00:09:13 +03:00
if setter is not None :
2020-03-21 11:23:34 +03:00
setter ( self , value )
2020-03-21 00:09:13 +03:00
@dbus.service.method ( __prop_interface , in_signature = " s " , out_signature = " a {sv} " )
def GetAll ( self , interface ) :
2020-04-09 01:26:21 +03:00
read_props = { }
props = self . __prop_mapping [ interface ]
2020-03-21 00:09:13 +03:00
for key , ( getter , setter ) in props . items ( ) :
if callable ( getter ) :
2020-04-09 01:26:21 +03:00
getter = getter ( self )
read_props [ key ] = getter
2020-03-21 00:09:13 +03:00
return read_props
def update_property ( self , interface , prop ) :
2020-04-09 01:26:21 +03:00
getter , setter = self . __prop_mapping [ interface ] [ prop ]
2020-03-21 00:09:13 +03:00
if callable ( getter ) :
2020-04-09 01:26:21 +03:00
value = getter ( self )
2020-03-21 00:09:13 +03:00
else :
2020-04-09 01:26:21 +03:00
value = getter
2020-03-21 00:09:13 +03:00
self . PropertiesChanged ( interface , { prop : value } , [ ] )
return value
# Root methods
@dbus.service.method ( __root_interface , in_signature = ' ' , out_signature = ' ' )
def Raise ( self ) :
self . window . present ( )
return
@dbus.service.method ( __root_interface , in_signature = ' ' , out_signature = ' ' )
def Quit ( self ) :
return
# Player methods
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def Next ( self ) :
self . client . next ( )
return
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def Previous ( self ) :
self . client . previous ( )
return
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def Pause ( self ) :
self . client . pause ( 1 )
return
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def PlayPause ( self ) :
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
2020-03-21 00:09:13 +03:00
if status [ ' state ' ] == ' play ' :
self . client . pause ( 1 )
else :
self . client . play ( )
return
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def Stop ( self ) :
self . client . stop ( )
return
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def Play ( self ) :
self . client . play ( )
return
@dbus.service.method ( __player_interface , in_signature = ' x ' , out_signature = ' ' )
def Seek ( self , offset ) : #TODO
2020-04-09 01:26:21 +03:00
status = self . client . status ( )
current , end = status [ ' time ' ] . split ( ' : ' )
current = int ( current )
end = int ( end )
offset = int ( offset ) / 1000000
2020-03-21 00:09:13 +03:00
if current + offset < = end :
2020-04-09 01:26:21 +03:00
position = current + offset
2020-03-21 00:09:13 +03:00
if position < 0 :
2020-04-09 01:26:21 +03:00
position = 0
2020-03-21 00:09:13 +03:00
self . client . seekid ( int ( status [ ' songid ' ] ) , position )
self . Seeked ( position * 1000000 )
return
@dbus.service.method ( __player_interface , in_signature = ' ox ' , out_signature = ' ' )
def SetPosition ( self , trackid , position ) :
2020-04-09 01:26:21 +03:00
song = self . client . currentsong ( )
2020-03-21 00:09:13 +03:00
# FIXME: use real dbus objects
if str ( trackid ) != ' /org/mpris/MediaPlayer2/Track/ %s ' % song [ ' id ' ] :
return
# Convert position to seconds
2020-04-09 01:26:21 +03:00
position = int ( position ) / 1000000
2020-03-21 00:09:13 +03:00
if position < = int ( song [ ' time ' ] ) :
self . client . seekid ( int ( song [ ' id ' ] ) , position )
self . Seeked ( position * 1000000 )
return
@dbus.service.signal ( __player_interface , signature = ' x ' )
def Seeked ( self , position ) :
return float ( position )
@dbus.service.method ( __player_interface , in_signature = ' ' , out_signature = ' ' )
def OpenUri ( self ) :
return
2020-01-28 20:39:18 +03:00
class Settings ( Gio . Settings ) :
2020-04-09 01:26:21 +03:00
BASE_KEY = " org.mpdevil "
2020-01-28 20:39:18 +03:00
def __init__ ( self ) :
2020-01-28 21:59:14 +03:00
super ( ) . __init__ ( schema = self . BASE_KEY )
2020-02-07 22:13:38 +03:00
if len ( self . get_value ( " profiles " ) ) < ( self . get_int ( " active-profile " ) + 1 ) :
self . set_int ( " active-profile " , 0 )
2020-01-28 20:39:18 +03:00
def array_append ( self , vtype , key , value ) : #append to Gio.Settings (self.settings) array
array = self . get_value ( key ) . unpack ( )
array . append ( value )
self . set_value ( key , GLib . Variant ( vtype , array ) )
def array_delete ( self , vtype , key , pos ) : #delete entry of Gio.Settings (self.settings) array
array = self . get_value ( key ) . unpack ( )
array . pop ( pos )
self . set_value ( key , GLib . Variant ( vtype , array ) )
def array_modify ( self , vtype , key , pos , value ) : #modify entry of Gio.Settings (self.settings) array
array = self . get_value ( key ) . unpack ( )
array [ pos ] = value
self . set_value ( key , GLib . Variant ( vtype , array ) )
2020-01-28 21:59:14 +03:00
def get_gtk_icon_size ( self , key ) :
icon_size = self . get_int ( key )
if icon_size == 16 :
return Gtk . IconSize . BUTTON
2020-01-29 18:42:45 +03:00
elif icon_size == 24 :
return Gtk . IconSize . LARGE_TOOLBAR
2020-01-28 21:59:14 +03:00
elif icon_size == 32 :
return Gtk . IconSize . DND
elif icon_size == 48 :
return Gtk . IconSize . DIALOG
else :
2020-01-29 18:42:45 +03:00
# return Gtk.IconSize.INVALID
2020-01-28 21:59:14 +03:00
raise ValueError
2020-02-26 01:37:27 +03:00
def get_artist_type ( self ) :
2020-03-31 17:07:40 +03:00
if self . get_boolean ( " use-album-artist " ) :
2020-02-26 01:37:27 +03:00
return ( " albumartist " )
2020-03-31 17:07:40 +03:00
else :
return ( " artist " )
2020-02-26 01:37:27 +03:00
2020-05-26 16:04:16 +03:00
class SongPopover ( Gtk . Popover ) :
2020-05-27 19:54:01 +03:00
def __init__ ( self , song , relative , x , y ) :
2020-05-26 16:04:16 +03:00
Gtk . Popover . __init__ ( self )
2020-05-27 19:54:01 +03:00
rect = Gdk . Rectangle ( )
rect . x = x
2020-05-27 23:16:35 +03:00
#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
2020-05-26 16:04:16 +03:00
self . set_pointing_to ( rect )
self . set_relative_to ( relative )
#Store
#(tag, display-value, tooltip)
self . store = Gtk . ListStore ( str , str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_can_focus ( False )
self . treeview . set_search_column ( - 1 )
self . treeview . set_tooltip_column ( 2 )
self . treeview . set_headers_visible ( False )
sel = self . treeview . get_selection ( )
sel . set_mode ( Gtk . SelectionMode . NONE )
2020-05-27 19:54:01 +03:00
frame = Gtk . Frame ( )
frame . add ( self . treeview )
frame . set_property ( " border-width " , 3 )
2020-05-26 16:04:16 +03:00
#Column
renderer_text = Gtk . CellRendererText ( width_chars = 50 , ellipsize = Pango . EllipsizeMode . MIDDLE , ellipsize_set = True )
renderer_text_ralign = Gtk . CellRendererText ( xalign = 1.0 )
self . column_tag = Gtk . TreeViewColumn ( _ ( " MPD-Tag " ) , renderer_text_ralign , text = 0 )
self . column_tag . set_property ( " resizable " , False )
self . treeview . append_column ( self . column_tag )
self . column_value = Gtk . TreeViewColumn ( _ ( " Value " ) , renderer_text , text = 1 )
self . column_value . set_property ( " resizable " , False )
self . treeview . append_column ( self . column_value )
#packing
2020-05-27 19:54:01 +03:00
self . add ( frame )
2020-05-26 16:04:16 +03:00
2020-06-04 21:36:34 +03:00
song = ClientHelper . song_to_str_dict ( song )
2020-05-26 16:04:16 +03:00
for tag , value in song . items ( ) :
tooltip = value . replace ( " & " , " & " )
if tag == " time " :
self . store . append ( [ tag + " : " , str ( datetime . timedelta ( seconds = int ( value ) ) ) , tooltip ] )
elif tag == " last-modified " :
time = datetime . datetime . strptime ( value , ' % Y- % m- %d T % H: % M: % SZ ' )
self . store . append ( [ tag + " : " , time . strftime ( ' %a %d % B % Y, % H: % M UTC ' ) , tooltip ] )
else :
self . store . append ( [ tag + " : " , value , tooltip ] )
2020-05-27 19:54:01 +03:00
frame . show_all ( )
2020-05-26 16:04:16 +03:00
# self.popup()
# self.treeview.queue_resize()
2020-05-18 18:45:47 +03:00
class SongsView ( Gtk . TreeView ) :
2020-06-04 20:34:36 +03:00
def __init__ ( self , client , store , file_column_id ) :
2020-05-18 18:45:47 +03:00
Gtk . TreeView . __init__ ( self )
2020-06-04 20:34:36 +03:00
self . set_model ( store )
2020-05-18 18:45:47 +03:00
self . set_search_column ( - 1 )
self . columns_autosize ( )
2020-01-18 00:13:58 +03:00
2020-03-31 00:48:46 +03:00
#add vars
2020-01-18 00:13:58 +03:00
self . client = client
2020-06-04 20:34:36 +03:00
self . store = store
self . file_column_id = file_column_id
2020-01-18 00:13:58 +03:00
2020-03-31 00:48:46 +03:00
#selection
2020-05-18 18:45:47 +03:00
self . selection = self . get_selection ( )
2020-01-18 00:13:58 +03:00
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
#connect
2020-05-18 18:45:47 +03:00
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 )
2020-01-18 00:13:58 +03:00
def on_row_activated ( self , widget , path , view_column ) :
2020-06-14 23:25:40 +03:00
self . client . files_to_playlist ( [ self . store [ path ] [ self . file_column_id ] ] , False , True )
2020-01-18 00:13:58 +03:00
2020-03-30 11:28:40 +03:00
def on_button_press_event ( self , widget , event ) :
2020-03-30 21:44:28 +03:00
if event . button == 1 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-30 11:28:40 +03:00
try :
2020-04-09 01:26:21 +03:00
path = widget . get_path_at_pos ( int ( event . x ) , int ( event . y ) ) [ 0 ]
2020-06-04 20:34:36 +03:00
self . client . files_to_playlist ( [ self . store [ path ] [ self . file_column_id ] ] , False )
2020-03-31 00:48:46 +03:00
except :
pass
elif event . button == 2 and event . type == Gdk . EventType . BUTTON_PRESS :
try :
2020-04-09 01:26:21 +03:00
path = widget . get_path_at_pos ( int ( event . x ) , int ( event . y ) ) [ 0 ]
2020-06-04 20:34:36 +03:00
self . client . files_to_playlist ( [ self . store [ path ] [ self . file_column_id ] ] , True )
2020-03-30 11:28:40 +03:00
except :
pass
2020-05-26 16:04:16 +03:00
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 ]
2020-06-04 20:34:36 +03:00
file_name = self . store [ path ] [ self . file_column_id ]
2020-06-04 21:36:34 +03:00
pop = SongPopover ( self . client . lsinfo ( file_name ) [ 0 ] , widget , int ( event . x ) , int ( event . y ) )
2020-05-26 16:04:16 +03:00
pop . popup ( )
2020-05-27 19:54:01 +03:00
pop . show_all ( )
2020-05-26 16:04:16 +03:00
except :
pass
2020-01-18 00:13:58 +03:00
2020-03-30 19:18:43 +03:00
def on_key_press_event ( self , widget , event ) :
2020-05-18 18:45:47 +03:00
self . handler_block ( self . key_press_event )
2020-03-30 21:44:28 +03:00
if event . keyval == 112 : #p
2020-03-30 19:18:43 +03:00
treeview , treeiter = self . selection . get_selected ( )
if not treeiter == None :
2020-06-04 20:34:36 +03:00
self . client . files_to_playlist ( [ self . store . get_value ( treeiter , self . file_column_id ) ] , False )
2020-03-31 00:48:46 +03:00
elif event . keyval == 97 : #a
treeview , treeiter = self . selection . get_selected ( )
if not treeiter == None :
2020-06-04 20:34:36 +03:00
self . client . files_to_playlist ( [ self . store . get_value ( treeiter , self . file_column_id ) ] , True )
2020-03-31 00:48:46 +03:00
# elif event.keyval == 65383: #menu key
2020-05-18 18:45:47 +03:00
self . handler_unblock ( self . key_press_event )
2020-03-30 19:18:43 +03:00
2020-03-31 00:48:46 +03:00
def clear ( self ) :
self . store . clear ( )
def count ( self ) :
return len ( self . store )
2020-05-18 21:47:04 +03:00
def get_files ( self ) :
return_list = [ ]
for row in self . store :
2020-06-04 20:34:36 +03:00
return_list . append ( row [ self . file_column_id ] )
2020-05-18 21:47:04 +03:00
return return_list
2020-03-31 00:48:46 +03:00
class AlbumDialog ( Gtk . Dialog ) :
def __init__ ( self , parent , client , settings , album , artist , year ) :
2020-04-02 21:40:43 +03:00
Gtk . Dialog . __init__ ( self , transient_for = parent )
2020-03-31 00:48:46 +03:00
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 )
2020-05-26 20:49:55 +03:00
2020-06-14 23:34:01 +03:00
#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 )
2020-05-26 20:49:55 +03:00
#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 )
2020-03-31 00:48:46 +03:00
2020-04-02 21:40:43 +03:00
#title
2020-06-14 23:34:01 +03:00
album_duration = ClientHelper . calc_display_length ( songs )
2020-04-02 21:40:43 +03:00
if year == " " :
2020-06-14 23:34:01 +03:00
self . set_title ( artist + " - " + album + " ( " + album_duration + " ) " )
2020-04-02 21:40:43 +03:00
else :
2020-06-14 23:34:01 +03:00
self . set_title ( artist + " - " + album + " ( " + year + " ) ( " + album_duration + " ) " )
2020-03-31 00:48:46 +03:00
2020-06-04 20:34:36 +03:00
#store
#(track, title (artist), duration, file)
self . store = Gtk . ListStore ( int , str , str , str )
2020-03-31 00:48:46 +03:00
#songs view
2020-06-04 20:34:36 +03:00
self . songs_view = SongsView ( self . client , self . store , 3 )
for s in songs :
2020-06-04 21:36:34 +03:00
song = ClientHelper . extend_song_for_display ( s )
2020-06-04 20:34:36 +03:00
if type ( song [ " title " ] ) == list : # could be impossible
title = ( ' , ' . join ( song [ " title " ] ) )
else :
title = song [ " title " ]
if type ( song [ " artist " ] ) == list :
2020-06-04 21:00:51 +03:00
try :
song [ " artist " ] . remove ( self . artist )
except :
pass
2020-06-04 20:34:36 +03:00
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 )
2020-03-31 00:48:46 +03:00
2020-05-18 18:45:47 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . songs_view )
2020-03-31 00:48:46 +03:00
#packing
2020-05-18 18:45:47 +03:00
self . vbox . pack_start ( scroll , True , True , 0 ) #vbox default widget of dialogs
2020-05-21 22:18:39 +03:00
self . vbox . set_spacing ( 3 )
2020-03-31 00:48:46 +03:00
self . show_all ( )
2020-01-18 00:13:58 +03:00
2020-03-30 23:20:14 +03:00
def open ( self ) :
2020-04-09 01:26:21 +03:00
response = self . run ( )
2020-03-30 23:20:14 +03:00
if response == Gtk . ResponseType . OK :
self . client . album_to_playlist ( self . album , self . artist , self . year , False )
elif response == Gtk . ResponseType . ACCEPT :
self . client . album_to_playlist ( self . album , self . artist , self . year , True )
elif response == Gtk . ResponseType . YES :
self . client . album_to_playlist ( self . album , self . artist , self . year , False , True )
2020-03-27 19:42:11 +03:00
class GenreSelect ( Gtk . ComboBoxText ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings ) :
2020-03-27 19:42:11 +03:00
Gtk . ComboBoxText . __init__ ( self )
2020-02-27 22:05:48 +03:00
#adding vars
self . client = client
self . settings = settings
#connect
2020-03-27 19:42:11 +03:00
self . changed = self . connect ( " changed " , self . on_changed )
2020-03-30 12:54:04 +03:00
self . update_signal = self . client . emitter . connect ( " update " , self . refresh )
2020-02-27 22:05:48 +03:00
2020-02-29 01:23:53 +03:00
def deactivate ( self ) :
2020-03-27 19:42:11 +03:00
self . set_active ( 0 )
2020-02-29 01:23:53 +03:00
2020-02-27 22:05:48 +03:00
def refresh ( self , * args ) :
2020-03-27 19:42:11 +03:00
self . handler_block ( self . changed )
self . remove_all ( )
self . append_text ( _ ( " all genres " ) )
2020-05-03 12:58:35 +03:00
for genre in self . client . comp_list ( " genre " ) :
2020-03-27 19:42:11 +03:00
self . append_text ( genre )
self . set_active ( 0 )
self . handler_unblock ( self . changed )
2020-02-27 22:05:48 +03:00
2020-03-25 00:23:45 +03:00
def clear ( self , * args ) :
2020-03-27 19:42:11 +03:00
self . handler_block ( self . changed )
self . remove_all ( )
self . handler_unblock ( self . changed )
2020-03-25 00:23:45 +03:00
2020-02-27 22:05:48 +03:00
def get_value ( self ) :
2020-03-27 19:42:11 +03:00
if self . get_active ( ) == 0 :
2020-02-27 22:05:48 +03:00
return None
2020-02-28 13:47:57 +03:00
else :
2020-03-27 19:42:11 +03:00
return self . get_active_text ( )
2020-02-27 22:05:48 +03:00
2020-03-27 19:42:11 +03:00
def on_changed ( self , * args ) :
2020-03-30 12:54:04 +03:00
self . client . emitter . handler_block ( self . update_signal )
self . client . emitter . emit ( " update " )
self . client . emitter . handler_unblock ( self . update_signal )
2020-02-27 22:05:48 +03:00
2020-03-31 18:36:41 +03:00
class ArtistView ( FocusFrame ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings , genre_select ) :
2020-03-31 18:36:41 +03:00
FocusFrame . __init__ ( self )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-02-26 01:37:27 +03:00
self . settings = settings
2020-02-27 22:05:48 +03:00
self . genre_select = genre_select
2020-01-11 13:25:15 +03:00
#artistStore
2020-03-26 01:36:59 +03:00
#(name, weight, initial-letter, weight-initials)
2020-04-09 01:26:21 +03:00
self . store = Gtk . ListStore ( str , Pango . Weight , str , Pango . Weight )
2020-01-11 13:25:15 +03:00
#TreeView
2020-04-09 01:26:21 +03:00
self . treeview = Gtk . TreeView ( model = self . store )
2020-03-26 01:36:59 +03:00
self . treeview . set_search_column ( 0 )
2020-02-26 01:37:27 +03:00
self . treeview . columns_autosize ( )
2020-03-26 01:36:59 +03:00
self . treeview . set_property ( " activate-on-single-click " , True )
2020-01-11 13:25:15 +03:00
#artistSelection
2020-04-09 01:26:21 +03:00
self . selection = self . treeview . get_selection ( )
2020-03-26 01:36:59 +03:00
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
2020-01-11 13:25:15 +03:00
2020-03-12 19:09:24 +03:00
#Columns
2020-04-09 01:26:21 +03:00
renderer_text_malign = Gtk . CellRendererText ( xalign = 0.5 )
self . column_initials = Gtk . TreeViewColumn ( " " , renderer_text_malign , text = 2 , weight = 3 )
2020-03-12 19:09:24 +03:00
self . column_initials . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
self . column_initials . set_property ( " resizable " , False )
self . column_initials . set_visible ( self . settings . get_boolean ( " show-initials " ) )
self . treeview . append_column ( self . column_initials )
2020-05-25 22:32:50 +03:00
renderer_text = Gtk . CellRendererText ( ellipsize = Pango . EllipsizeMode . END , ellipsize_set = True )
2020-04-09 01:26:21 +03:00
self . column_name = Gtk . TreeViewColumn ( " " , renderer_text , text = 0 , weight = 1 )
2020-01-11 13:25:15 +03:00
self . column_name . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
2020-02-26 01:37:27 +03:00
self . column_name . set_property ( " resizable " , False )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_name )
2020-03-31 18:36:41 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . treeview )
2020-02-02 20:08:59 +03:00
#connect
2020-03-26 01:36:59 +03:00
self . treeview . connect ( " row-activated " , self . on_row_activated )
2020-03-31 17:07:40 +03:00
self . settings . connect ( " changed::use-album-artist " , self . refresh )
2020-03-12 19:09:24 +03:00
self . settings . connect ( " changed::show-initials " , self . on_show_initials_settings_changed )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " update " , self . refresh )
2020-02-02 20:08:59 +03:00
2020-03-31 18:36:41 +03:00
self . set_widget ( self . treeview )
self . add ( scroll )
2020-01-11 13:25:15 +03:00
2020-03-26 01:36:59 +03:00
@GObject.Signal
def artists_changed ( self ) :
pass
2020-02-01 16:12:56 +03:00
def clear ( self ) :
self . store . clear ( )
2020-02-25 00:31:39 +03:00
def refresh ( self , * args ) :
self . selection . set_mode ( Gtk . SelectionMode . NONE )
self . clear ( )
2020-02-28 16:27:58 +03:00
if self . settings . get_artist_type ( ) == " albumartist " :
self . column_name . set_title ( _ ( " Album Artist " ) )
else :
self . column_name . set_title ( _ ( " Artist " ) )
2020-03-26 01:36:59 +03:00
self . store . append ( [ _ ( " all artists " ) , Pango . Weight . BOOK , " " , Pango . Weight . BOOK ] )
2020-02-27 22:05:48 +03:00
genre = self . genre_select . get_value ( )
if genre == None :
2020-05-03 12:58:35 +03:00
artists = self . client . comp_list ( self . settings . get_artist_type ( ) )
2020-02-27 22:05:48 +03:00
else :
2020-05-03 12:58:35 +03:00
artists = self . client . comp_list ( self . settings . get_artist_type ( ) , " genre " , genre )
2020-03-04 01:54:20 +03:00
current_char = " "
2020-02-27 22:05:48 +03:00
for artist in artists :
2020-03-04 01:54:20 +03:00
try :
if current_char != artist [ 0 ] :
2020-03-26 01:36:59 +03:00
self . store . append ( [ artist , Pango . Weight . BOOK , artist [ 0 ] , Pango . Weight . BOLD ] )
2020-03-04 01:54:20 +03:00
current_char = artist [ 0 ]
2020-03-12 19:09:24 +03:00
else :
2020-03-26 01:36:59 +03:00
self . store . append ( [ artist , Pango . Weight . BOOK , " " , Pango . Weight . BOOK ] )
2020-03-04 01:54:20 +03:00
except :
2020-03-26 01:36:59 +03:00
self . store . append ( [ artist , Pango . Weight . BOOK , " " , Pango . Weight . BOOK ] )
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
2020-01-11 13:25:15 +03:00
2020-02-01 16:36:57 +03:00
def get_selected_artists ( self ) :
artists = [ ]
2020-03-26 01:36:59 +03:00
if self . store [ Gtk . TreePath ( 0 ) ] [ 1 ] == Pango . Weight . BOLD :
for row in self . store :
artists . append ( row [ 0 ] )
return artists [ 1 : ]
else :
for row in self . store :
if row [ 1 ] == Pango . Weight . BOLD :
artists . append ( row [ 0 ] )
break
return artists
2020-02-01 16:36:57 +03:00
2020-05-19 18:23:15 +03:00
def highlight_selected ( self ) :
for path , row in enumerate ( self . store ) :
if row [ 1 ] == Pango . Weight . BOLD :
self . treeview . set_cursor ( path , None , False )
break
2020-03-26 01:36:59 +03:00
def on_row_activated ( self , widget , path , view_column ) :
2020-04-09 01:34:19 +03:00
for row in self . store : #reset bold text
row [ 1 ] = Pango . Weight . BOOK
2020-03-26 01:36:59 +03:00
self . store [ path ] [ 1 ] = Pango . Weight . BOLD
self . emit ( " artists_changed " )
2020-03-12 19:09:24 +03:00
def on_show_initials_settings_changed ( self , * args ) :
self . column_initials . set_visible ( self . settings . get_boolean ( " show-initials " ) )
2020-03-30 23:20:14 +03:00
class AlbumIconView ( Gtk . IconView ) :
2020-02-27 22:05:48 +03:00
def __init__ ( self , client , settings , genre_select , window ) :
2020-02-01 14:45:34 +03:00
Gtk . IconView . __init__ ( self )
2020-01-11 13:25:15 +03:00
#adding vars
self . settings = settings
self . client = client
2020-02-27 22:05:48 +03:00
self . genre_select = genre_select
2020-02-01 14:45:34 +03:00
self . window = window
2020-02-25 00:31:39 +03:00
self . stop_flag = True
2020-06-17 20:21:07 +03:00
self . button_event = ( None , None )
2020-01-11 13:25:15 +03:00
2020-05-27 22:08:01 +03:00
#cover, display_label, display_label_artist, tooltip(titles), album, year, artist
self . store = Gtk . ListStore ( GdkPixbuf . Pixbuf , str , str , str , str , str , str )
2020-02-28 18:03:45 +03:00
self . sort_settings ( )
2020-01-11 13:25:15 +03:00
#iconview
2020-02-01 14:45:34 +03:00
self . set_model ( self . store )
self . set_pixbuf_column ( 0 )
2020-05-27 22:08:01 +03:00
self . set_markup_column ( 1 )
2020-02-01 14:45:34 +03:00
self . set_item_width ( 0 )
self . tooltip_settings ( )
2020-01-11 13:25:15 +03:00
2020-02-01 14:45:34 +03:00
#connect
2020-03-30 23:20:14 +03:00
self . connect ( " item-activated " , self . on_item_activated )
2020-06-17 20:21:07 +03:00
self . connect ( " button-release-event " , self . on_button_release_event )
2020-03-30 23:20:14 +03:00
self . connect ( " button-press-event " , self . on_button_press_event )
self . key_press_event = self . connect ( " key-press-event " , self . on_key_press_event )
2020-02-01 14:45:34 +03:00
self . settings . connect ( " changed::show-album-view-tooltips " , self . tooltip_settings )
2020-02-28 18:03:45 +03:00
self . settings . connect ( " changed::sort-albums-by-year " , self . sort_settings )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
@GObject.Signal
2020-03-26 19:01:15 +03:00
def done ( self ) :
2020-03-29 19:06:37 +03:00
self . stop_flag = True
2020-02-25 00:31:39 +03:00
pass
2020-02-01 14:45:34 +03:00
def tooltip_settings ( self , * args ) :
2020-01-11 13:25:15 +03:00
if self . settings . get_boolean ( " show-album-view-tooltips " ) :
2020-05-27 22:08:01 +03:00
self . set_tooltip_column ( 3 )
2020-01-11 13:25:15 +03:00
else :
2020-02-01 14:45:34 +03:00
self . set_tooltip_column ( - 1 )
2020-02-28 18:03:45 +03:00
def sort_settings ( self , * args ) :
if self . settings . get_boolean ( " sort-albums-by-year " ) :
2020-05-27 22:08:01 +03:00
self . store . set_sort_column_id ( 5 , Gtk . SortType . ASCENDING )
2020-02-28 18:03:45 +03:00
else :
self . store . set_sort_column_id ( 1 , Gtk . SortType . ASCENDING )
2020-03-29 19:06:37 +03:00
return False
2020-02-28 18:03:45 +03:00
2020-03-30 23:20:14 +03:00
def add_row ( self , row , cover , size ) :
2020-03-26 19:01:15 +03:00
row [ 0 ] = cover . get_pixbuf ( size )
self . store . append ( row )
return False
2020-02-01 14:45:34 +03:00
def populate ( self , artists ) :
2020-03-29 19:06:37 +03:00
self . stop_flag = False
2020-03-26 19:01:15 +03:00
#prepare albmus list
self . store . clear ( )
2020-05-27 22:08:01 +03:00
if len ( artists ) > 1 :
self . set_markup_column ( 2 )
else :
self . set_markup_column ( 1 )
2020-02-25 00:31:39 +03:00
albums = [ ]
2020-02-27 22:05:48 +03:00
genre = self . genre_select . get_value ( )
2020-03-26 19:01:15 +03:00
artist_type = self . settings . get_artist_type ( )
2020-02-01 14:45:34 +03:00
for artist in artists :
2020-03-30 13:28:30 +03:00
try : #client cloud meanwhile disconnect
if not self . stop_flag :
if genre == None :
2020-05-03 12:58:35 +03:00
album_candidates = self . client . comp_list ( " album " , artist_type , artist )
2020-03-30 13:28:30 +03:00
else :
2020-05-03 12:58:35 +03:00
album_candidates = self . client . comp_list ( " album " , artist_type , artist , " genre " , genre )
2020-03-30 13:28:30 +03:00
for album in album_candidates :
2020-05-03 12:58:35 +03:00
years = self . client . comp_list ( " date " , " album " , album , artist_type , artist )
2020-03-30 13:28:30 +03:00
for year in years :
songs = self . client . find ( " album " , album , " date " , year , artist_type , artist )
albums . append ( { " artist " : artist , " album " : album , " year " : year , " songs " : songs } )
while Gtk . events_pending ( ) :
Gtk . main_iteration_do ( True )
2020-03-29 19:06:37 +03:00
else :
2020-03-30 13:28:30 +03:00
GLib . idle_add ( self . emit , " done " )
return
except :
2020-03-29 19:06:37 +03:00
GLib . idle_add ( self . emit , " done " )
return
#display albums
2020-04-01 18:39:59 +03:00
if self . settings . get_boolean ( " sort-albums-by-year " ) :
2020-04-09 01:26:21 +03:00
albums = sorted ( albums , key = lambda k : k [ ' year ' ] )
2020-04-01 18:39:59 +03:00
else :
2020-04-09 01:26:21 +03:00
albums = sorted ( albums , key = lambda k : k [ ' album ' ] )
2020-03-29 19:06:37 +03:00
music_lib = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ]
size = self . settings . get_int ( " album-cover " )
2020-04-24 23:20:17 +03:00
for i , album in enumerate ( albums ) :
2020-03-29 19:06:37 +03:00
if not self . stop_flag :
cover = Cover ( lib_path = music_lib , song_file = album [ " songs " ] [ 0 ] [ " file " ] )
#tooltip
2020-06-04 21:36:34 +03:00
length_human_readable = ClientHelper . calc_display_length ( album [ " songs " ] )
2020-05-12 18:56:59 +03:00
try :
discs = int ( album [ " songs " ] [ - 1 ] [ " disc " ] )
except :
discs = 1
if discs > 1 :
tooltip = ( _ ( " %(total_tracks)i titles on %(discs)i discs ( %(total_length)s ) " ) % { " total_tracks " : len ( album [ " songs " ] ) , " discs " : discs , " total_length " : length_human_readable } )
else :
tooltip = ( _ ( " %(total_tracks)i titles ( %(total_length)s ) " ) % { " total_tracks " : len ( album [ " songs " ] ) , " total_length " : length_human_readable } )
2020-05-27 22:08:01 +03:00
display_label = " <b> " + album [ " album " ] + " </b> "
if album [ " year " ] != " " :
display_label = display_label + " ( " + album [ " year " ] + " ) "
display_label_artist = display_label + " \n " + album [ " artist " ]
display_label = display_label . replace ( " & " , " & " )
display_label_artist = display_label_artist . replace ( " & " , " & " )
GLib . idle_add ( self . add_row , [ None , display_label , display_label_artist , tooltip , album [ " album " ] , album [ " year " ] , album [ " artist " ] ] , cover , size )
2020-04-24 23:20:17 +03:00
if i % 16 == 0 :
2020-03-29 19:06:37 +03:00
while Gtk . events_pending ( ) :
Gtk . main_iteration_do ( True )
else :
break
GLib . idle_add ( self . emit , " done " )
2020-02-01 14:45:34 +03:00
2020-04-07 02:37:53 +03:00
def scroll_to_selected_album ( self ) :
2020-06-04 21:36:34 +03:00
song = ClientHelper . song_to_first_str_dict ( self . client . currentsong ( ) )
2020-02-01 14:45:34 +03:00
self . unselect_all ( )
row_num = len ( self . store )
for i in range ( 0 , row_num ) :
path = Gtk . TreePath ( i )
2020-04-09 01:26:21 +03:00
treeiter = self . store . get_iter ( path )
2020-05-27 22:08:01 +03:00
if self . store . get_value ( treeiter , 4 ) == song [ " album " ] :
2020-02-25 15:59:43 +03:00
self . set_cursor ( path , None , False )
2020-02-01 14:45:34 +03:00
self . select_path ( path )
self . scroll_to_path ( path , True , 0 , 0 )
break
2020-03-30 23:20:14 +03:00
def path_to_playlist ( self , path , add , force = False ) :
2020-05-27 22:08:01 +03:00
album = self . store [ path ] [ 4 ]
year = self . store [ path ] [ 5 ]
artist = self . store [ path ] [ 6 ]
2020-03-30 23:20:14 +03:00
self . client . album_to_playlist ( album , artist , year , add , force )
def open_album_dialog ( self , path ) :
if self . client . connected ( ) :
2020-05-27 22:08:01 +03:00
album = self . store [ path ] [ 4 ]
year = self . store [ path ] [ 5 ]
artist = self . store [ path ] [ 6 ]
2020-04-09 01:26:21 +03:00
album_dialog = AlbumDialog ( self . window , self . client , self . settings , album , artist , year )
2020-03-30 23:20:14 +03:00
album_dialog . open ( )
album_dialog . destroy ( )
def on_button_press_event ( self , widget , event ) :
2020-06-17 20:21:07 +03:00
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 ) :
2020-04-09 01:26:21 +03:00
path = widget . get_path_at_pos ( int ( event . x ) , int ( event . y ) )
2020-02-01 14:45:34 +03:00
if not path == None :
2020-06-17 20:21:07 +03:00
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_RELEASE :
self . path_to_playlist ( path , True )
elif event . button == 3 and event . type == Gdk . EventType . BUTTON_RELEASE :
self . open_album_dialog ( path )
2020-03-30 23:20:14 +03:00
def on_key_press_event ( self , widget , event ) :
self . handler_block ( self . key_press_event )
if event . keyval == 112 : #p
paths = self . get_selected_items ( )
if not len ( paths ) == 0 :
self . path_to_playlist ( paths [ 0 ] , False )
elif event . keyval == 97 : #a
paths = self . get_selected_items ( )
if not len ( paths ) == 0 :
self . path_to_playlist ( paths [ 0 ] , True )
elif event . keyval == 65383 : #menu key
paths = self . get_selected_items ( )
if not len ( paths ) == 0 :
self . open_album_dialog ( paths [ 0 ] )
self . handler_unblock ( self . key_press_event )
def on_item_activated ( self , widget , path ) :
2020-02-01 14:45:34 +03:00
treeiter = self . store . get_iter ( path )
2020-05-27 22:08:01 +03:00
selected_album = self . store . get_value ( treeiter , 4 )
selected_album_year = self . store . get_value ( treeiter , 5 )
selected_artist = self . store . get_value ( treeiter , 6 )
2020-02-01 14:45:34 +03:00
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , False , True )
2020-03-31 18:36:41 +03:00
class AlbumView ( FocusFrame ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings , genre_select , window ) :
2020-03-31 18:36:41 +03:00
FocusFrame . __init__ ( self )
2020-02-01 14:45:34 +03:00
#adding vars
self . settings = settings
self . client = client
2020-02-27 22:05:48 +03:00
self . genre_select = genre_select
2020-02-01 14:45:34 +03:00
self . window = window
2020-02-25 00:31:39 +03:00
self . artists = [ ]
2020-03-29 19:06:37 +03:00
self . done = True
2020-03-26 19:01:15 +03:00
self . pending = [ ]
2020-02-01 14:45:34 +03:00
2020-03-31 18:36:41 +03:00
#iconview
2020-02-27 22:05:48 +03:00
self . iconview = AlbumIconView ( self . client , self . settings , self . genre_select , self . window )
2020-02-25 00:31:39 +03:00
2020-03-31 18:36:41 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . iconview )
2020-03-10 15:50:36 +03:00
#connect
self . settings . connect ( " changed::album-cover " , self . on_settings_changed )
2020-03-26 19:01:15 +03:00
self . iconview . connect ( " done " , self . on_done )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " update " , self . clear )
2020-03-31 17:07:40 +03:00
self . settings . connect ( " changed::use-album-artist " , self . clear )
2020-03-10 15:50:36 +03:00
2020-03-31 18:36:41 +03:00
self . set_widget ( self . iconview )
self . add ( scroll )
2020-02-25 00:31:39 +03:00
2020-03-27 19:42:11 +03:00
def clear ( self , * args ) :
2020-03-30 13:28:30 +03:00
if self . done :
self . iconview . store . clear ( )
elif not self . clear in self . pending :
self . iconview . stop_flag = True
self . pending . append ( self . clear )
2020-02-01 16:12:56 +03:00
def refresh ( self , artists ) :
2020-03-26 19:01:15 +03:00
self . artists = artists
2020-03-29 19:06:37 +03:00
if self . done :
self . done = False
self . populate ( )
elif not self . populate in self . pending :
self . iconview . stop_flag = True
self . pending . append ( self . populate )
def populate ( self ) :
self . iconview . populate ( self . artists )
2020-02-01 14:45:34 +03:00
2020-02-03 01:10:33 +03:00
def scroll_to_selected_album ( self ) :
2020-03-26 19:01:15 +03:00
if self . done :
self . iconview . scroll_to_selected_album ( )
elif not self . scroll_to_selected_album in self . pending :
self . pending . append ( self . scroll_to_selected_album )
def on_done ( self , * args ) :
self . done = True
pending = self . pending
self . pending = [ ]
for p in pending :
try :
p ( )
except :
pass
2020-03-22 19:05:51 +03:00
2020-03-10 15:50:36 +03:00
def on_settings_changed ( self , * args ) :
2020-03-26 19:01:15 +03:00
if self . done :
2020-03-29 19:06:37 +03:00
self . populate ( )
2020-03-10 15:50:36 +03:00
2020-03-30 18:08:59 +03:00
class MainCover ( Gtk . Frame ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings , window ) :
2020-03-30 18:08:59 +03:00
Gtk . Frame . __init__ ( self )
2020-05-18 16:53:29 +03:00
#diable auto resize
self . set_halign ( 3 )
self . set_valign ( 3 )
2020-03-30 18:08:59 +03:00
#css
style_context = self . get_style_context ( )
2020-04-09 01:26:21 +03:00
provider = Gtk . CssProvider ( )
css = b """ * { background-color: @theme_base_color; border-radius: 6px;} """
2020-03-30 18:08:59 +03:00
provider . load_from_data ( css )
style_context . add_provider ( provider , 800 )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-03-03 15:41:46 +03:00
self . settings = settings
2020-02-03 01:10:33 +03:00
self . window = window
2020-03-03 15:41:46 +03:00
2020-03-30 18:08:59 +03:00
#event box
event_box = Gtk . EventBox ( )
event_box . set_property ( " border-width " , 6 )
2020-03-03 15:41:46 +03:00
#cover
self . cover = Gtk . Image . new ( )
2020-05-18 16:32:04 +03:00
size = self . settings . get_int ( " track-cover " )
self . cover . set_from_pixbuf ( Cover ( lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = None ) . get_pixbuf ( size ) ) #set to fallback cover
#set default size
self . cover . set_size_request ( size , size )
2020-03-03 15:41:46 +03:00
#connect
2020-03-30 18:08:59 +03:00
event_box . connect ( " button-press-event " , self . on_button_press_event )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " playing_file_changed " , self . refresh )
2020-03-10 15:50:36 +03:00
self . settings . connect ( " changed::track-cover " , self . on_settings_changed )
2020-03-03 15:41:46 +03:00
2020-03-30 18:08:59 +03:00
event_box . add ( self . cover )
self . add ( event_box )
2020-03-03 15:41:46 +03:00
def refresh ( self , * args ) :
2020-03-22 20:13:40 +03:00
try :
current_song = self . client . currentsong ( )
2020-03-22 16:25:04 +03:00
song_file = current_song [ ' file ' ]
2020-03-22 20:13:40 +03:00
except :
song_file = None
2020-03-22 16:25:04 +03:00
self . cover . set_from_pixbuf ( Cover ( lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = song_file ) . get_pixbuf ( self . settings . get_int ( " track-cover " ) ) )
2020-03-03 15:41:46 +03:00
2020-03-22 19:05:51 +03:00
def clear ( self , * args ) :
self . cover . set_from_pixbuf ( Cover ( lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = None ) . get_pixbuf ( self . settings . get_int ( " track-cover " ) ) )
self . song_file = None
2020-03-03 15:41:46 +03:00
def on_button_press_event ( self , widget , event ) :
if self . client . connected ( ) :
2020-06-04 21:36:34 +03:00
song = ClientHelper . song_to_first_str_dict ( self . client . currentsong ( ) )
2020-03-03 15:41:46 +03:00
if not song == { } :
try :
artist = song [ self . settings . get_artist_type ( ) ]
except :
2020-03-31 17:07:40 +03:00
try :
artist = song [ " artist " ]
except :
artist = " "
2020-03-03 15:41:46 +03:00
try :
album = song [ " album " ]
except :
album = " "
try :
album_year = song [ " date " ]
except :
album_year = " "
2020-03-30 21:44:28 +03:00
if event . button == 1 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 15:41:46 +03:00
self . client . album_to_playlist ( album , artist , album_year , False )
2020-03-30 21:44:28 +03:00
elif event . button == 2 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 15:41:46 +03:00
self . client . album_to_playlist ( album , artist , album_year , True )
2020-03-30 21:44:28 +03:00
elif event . button == 3 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-04-09 01:26:21 +03:00
album_dialog = AlbumDialog ( self . window , self . client , self . settings , album , artist , album_year )
2020-03-30 23:20:14 +03:00
album_dialog . open ( )
2020-03-03 15:41:46 +03:00
album_dialog . destroy ( )
2020-03-10 15:50:36 +03:00
def on_settings_changed ( self , * args ) :
2020-05-18 16:32:04 +03:00
size = self . settings . get_int ( " track-cover " )
self . cover . set_size_request ( size , size )
2020-03-10 15:50:36 +03:00
self . song_file = None
self . refresh ( )
2020-03-22 19:05:51 +03:00
class PlaylistView ( Gtk . Box ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings ) :
2020-03-03 15:41:46 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL )
#adding vars
self . client = client
2020-03-04 18:39:59 +03:00
self . settings = settings
2020-02-25 00:31:39 +03:00
self . playlist_version = None
2020-01-11 13:25:15 +03:00
#Store
2020-03-04 18:39:59 +03:00
#(track, disc, title, artist, album, duration, date, genre, file, weight)
2020-04-09 01:26:21 +03:00
self . store = Gtk . ListStore ( str , str , str , str , str , str , str , str , str , Pango . Weight )
2020-01-11 13:25:15 +03:00
#TreeView
2020-04-09 01:26:21 +03:00
self . treeview = Gtk . TreeView ( model = self . store )
2020-03-26 01:36:59 +03:00
self . treeview . set_search_column ( 2 )
2020-02-25 00:31:39 +03:00
self . treeview . set_property ( " activate-on-single-click " , True )
2020-01-11 13:25:15 +03:00
#selection
2020-04-09 01:26:21 +03:00
self . selection = self . treeview . get_selection ( )
2020-01-11 13:25:15 +03:00
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
#Column
2020-05-25 22:32:50 +03:00
renderer_text = Gtk . CellRendererText ( ellipsize = Pango . EllipsizeMode . END , ellipsize_set = True )
2020-04-09 01:26:21 +03:00
renderer_text_ralign = Gtk . CellRendererText ( xalign = 1.0 )
2020-03-04 18:39:59 +03:00
self . columns = [ None , None , None , None , None , None , None , None ]
2020-01-11 13:25:15 +03:00
2020-04-09 01:26:21 +03:00
self . columns [ 0 ] = Gtk . TreeViewColumn ( _ ( " No " ) , renderer_text_ralign , text = 0 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 0 ] . set_property ( " resizable " , True )
2020-01-11 13:25:15 +03:00
2020-04-09 01:26:21 +03:00
self . columns [ 1 ] = Gtk . TreeViewColumn ( _ ( " Disc " ) , renderer_text_ralign , text = 1 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 1 ] . set_property ( " resizable " , True )
2020-01-11 13:25:15 +03:00
2020-04-09 01:26:21 +03:00
self . columns [ 2 ] = Gtk . TreeViewColumn ( _ ( " Title " ) , renderer_text , text = 2 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 2 ] . set_property ( " resizable " , True )
2020-04-09 01:26:21 +03:00
self . columns [ 3 ] = Gtk . TreeViewColumn ( _ ( " Artist " ) , renderer_text , text = 3 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 3 ] . set_property ( " resizable " , True )
2020-04-09 01:26:21 +03:00
self . columns [ 4 ] = Gtk . TreeViewColumn ( _ ( " Album " ) , renderer_text , text = 4 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 4 ] . set_property ( " resizable " , True )
2020-04-09 01:26:21 +03:00
self . columns [ 5 ] = Gtk . TreeViewColumn ( _ ( " Length " ) , renderer_text , text = 5 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 5 ] . set_property ( " resizable " , True )
2020-01-11 13:25:15 +03:00
2020-04-09 01:26:21 +03:00
self . columns [ 6 ] = Gtk . TreeViewColumn ( _ ( " Year " ) , renderer_text , text = 6 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 6 ] . set_property ( " resizable " , True )
2020-04-09 01:26:21 +03:00
self . columns [ 7 ] = Gtk . TreeViewColumn ( _ ( " Genre " ) , renderer_text , text = 7 , weight = 9 )
2020-03-04 18:39:59 +03:00
self . columns [ 7 ] . set_property ( " resizable " , True )
self . load_settings ( )
2020-01-11 13:25:15 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . treeview )
2020-03-31 18:36:41 +03:00
#frame
frame = FocusFrame ( )
frame . set_widget ( self . treeview )
frame . add ( scroll )
2020-01-11 13:25:15 +03:00
#audio infos
audio = AudioType ( self . client )
2020-05-26 16:04:16 +03:00
audio . set_margin_end ( 3 )
audio . set_xalign ( 1 )
audio . set_ellipsize ( Pango . EllipsizeMode . END )
2020-01-11 13:25:15 +03:00
2020-01-27 22:27:35 +03:00
#playlist info
self . playlist_info = Gtk . Label ( )
2020-05-26 16:04:16 +03:00
self . playlist_info . set_margin_start ( 3 )
2020-01-27 22:27:35 +03:00
self . playlist_info . set_xalign ( 0 )
self . playlist_info . set_ellipsize ( Pango . EllipsizeMode . END )
#status bar
2020-03-24 21:18:29 +03:00
status_bar = Gtk . Box ( orientation = Gtk . Orientation . HORIZONTAL , spacing = 12 )
2020-05-26 16:04:16 +03:00
status_bar . set_property ( " border-width " , 3 )
2020-01-27 22:27:35 +03:00
status_bar . pack_start ( self . playlist_info , True , True , 0 )
status_bar . pack_end ( audio , False , False , 0 )
2020-01-11 13:25:15 +03:00
#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 )
2020-03-26 13:53:36 +03:00
self . treeview . connect ( " button-press-event " , self . on_button_press_event )
2020-01-11 13:25:15 +03:00
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " playlist " , self . on_playlist_changed )
self . client . emitter . connect ( " playing_file_changed " , self . on_file_changed )
self . client . emitter . connect ( " disconnected " , self . on_disconnected )
2020-02-25 00:31:39 +03:00
2020-03-04 18:39:59 +03:00
self . settings . connect ( " changed::column-visibilities " , self . load_settings )
self . settings . connect ( " changed::column-permutation " , self . load_settings )
2020-01-11 13:25:15 +03:00
#packing
2020-03-31 18:36:41 +03:00
self . pack_start ( frame , True , True , 0 )
2020-03-24 21:18:29 +03:00
self . pack_start ( Gtk . Separator . new ( orientation = Gtk . Orientation . HORIZONTAL ) , False , False , 0 )
2020-01-27 22:27:35 +03:00
self . pack_end ( status_bar , False , False , 0 )
2020-01-11 13:25:15 +03:00
2020-03-04 18:39:59 +03:00
def save_settings ( self ) : #only saves the column sizes
columns = self . treeview . get_columns ( )
2020-03-04 20:55:43 +03:00
permutation = self . settings . get_value ( " column-permutation " ) . unpack ( )
sizes = [ 0 ] * len ( permutation )
for i in range ( len ( permutation ) ) :
sizes [ permutation [ i ] ] = columns [ i ] . get_width ( )
2020-03-04 18:39:59 +03:00
self . settings . set_value ( " column-sizes " , GLib . Variant ( " ai " , sizes ) )
def load_settings ( self , * args ) :
columns = self . treeview . get_columns ( )
for column in columns :
self . treeview . remove_column ( column )
sizes = self . settings . get_value ( " column-sizes " ) . unpack ( )
visibilities = self . settings . get_value ( " column-visibilities " ) . unpack ( )
for i in self . settings . get_value ( " column-permutation " ) :
2020-03-04 20:55:43 +03:00
if sizes [ i ] > 0 :
self . columns [ i ] . set_fixed_width ( sizes [ i ] )
self . columns [ i ] . set_visible ( visibilities [ i ] )
2020-03-04 18:39:59 +03:00
self . treeview . append_column ( self . columns [ i ] )
2020-02-01 14:45:34 +03:00
def scroll_to_selected_title ( self ) :
treeview , treeiter = self . selection . get_selected ( )
if not treeiter == None :
path = treeview . get_path ( treeiter )
2020-03-29 22:34:51 +03:00
self . treeview . scroll_to_cell ( path , None , True , 0.25 )
2020-02-01 14:45:34 +03:00
2020-01-29 19:29:47 +03:00
def refresh_playlist_info ( self ) :
songs = self . client . playlistinfo ( )
if not songs == [ ] :
2020-06-04 21:36:34 +03:00
whole_length_human_readable = ClientHelper . calc_display_length ( songs )
2020-01-29 19:29:47 +03:00
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 ( " " )
2020-02-29 10:23:42 +03:00
def refresh_selection ( self ) : #Gtk.TreePath(len(self.store) is used to generate an invalid TreePath (needed to unset cursor)
self . treeview . set_cursor ( Gtk . TreePath ( len ( self . store ) ) , None , False )
2020-04-04 17:23:09 +03:00
for row in self . store : #reset bold text
row [ 9 ] = Pango . Weight . BOOK
2020-02-01 14:45:34 +03:00
try :
song = self . client . status ( ) [ " song " ]
2020-04-09 01:26:21 +03:00
path = Gtk . TreePath ( int ( song ) )
2020-02-29 10:23:42 +03:00
self . selection . select_path ( path )
2020-03-26 13:53:36 +03:00
self . store [ path ] [ 9 ] = Pango . Weight . BOLD
2020-03-29 22:34:51 +03:00
self . scroll_to_selected_title ( )
2020-02-01 14:45:34 +03:00
except :
self . selection . unselect_all ( )
2020-02-25 00:31:39 +03:00
def clear ( self , * args ) :
self . playlist_info . set_text ( " " )
self . store . clear ( )
self . playlist_version = None
2020-01-11 13:25:15 +03:00
2020-03-26 13:53:36 +03:00
def remove_song ( self , path ) :
self . client . delete ( path ) #bad song index possible
self . store . remove ( self . store . get_iter ( path ) )
self . playlist_version = self . client . status ( ) [ " playlist " ]
2020-01-11 13:25:15 +03:00
def on_key_press_event ( self , widget , event ) :
self . treeview . handler_block ( self . key_press_event )
if event . keyval == 65535 : #entf
2020-03-26 13:53:36 +03:00
treeview , treeiter = self . selection . get_selected ( )
if not treeiter == None :
path = self . store . get_path ( treeiter )
2020-01-11 13:25:15 +03:00
try :
2020-03-26 13:53:36 +03:00
self . remove_song ( path )
2020-01-11 13:25:15 +03:00
except :
2020-03-26 13:53:36 +03:00
pass
2020-01-11 13:25:15 +03:00
self . treeview . handler_unblock ( self . key_press_event )
2020-03-26 13:53:36 +03:00
def on_button_press_event ( self , widget , event ) :
2020-03-30 21:44:28 +03:00
if event . button == 2 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-26 13:53:36 +03:00
try :
2020-04-09 01:26:21 +03:00
path = widget . get_path_at_pos ( int ( event . x ) , int ( event . y ) ) [ 0 ]
2020-03-26 13:53:36 +03:00
self . remove_song ( path )
except :
pass
2020-05-26 16:04:16 +03:00
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 ]
2020-06-04 21:36:34 +03:00
pop = SongPopover ( self . client . playlistinfo ( path ) [ 0 ] , widget , int ( event . x ) , int ( event . y ) )
2020-05-26 16:04:16 +03:00
pop . popup ( )
except :
pass
2020-01-11 13:25:15 +03:00
2020-03-22 19:05:51 +03:00
def on_row_activated ( self , widget , path , view_column ) :
self . client . play ( path )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
def on_playlist_changed ( self , * args ) :
songs = [ ]
if not self . playlist_version == None :
songs = self . client . plchanges ( self . playlist_version )
else :
songs = self . client . playlistinfo ( )
if not songs == [ ] :
self . playlist_info . set_text ( " " )
2020-04-07 02:25:53 +03:00
for s in songs :
2020-06-04 21:36:34 +03:00
song = ClientHelper . extend_song_for_display ( ClientHelper . song_to_str_dict ( s ) )
2020-02-25 00:31:39 +03:00
try :
treeiter = self . store . get_iter ( song [ " pos " ] )
2020-05-13 20:29:39 +03:00
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 )
2020-02-25 00:31:39 +03:00
except :
2020-05-13 20:29:39 +03:00
self . store . append ( [ song [ " track " ] , song [ " disc " ] , song [ " title " ] , song [ " artist " ] , song [ " album " ] , song [ " human_duration " ] , song [ " date " ] , song [ " genre " ] , song [ " file " ] , Pango . Weight . BOOK ] )
2020-03-02 17:48:10 +03:00
for i in reversed ( range ( int ( self . client . status ( ) [ " playlistlength " ] ) , len ( self . store ) ) ) :
treeiter = self . store . get_iter ( i )
self . store . remove ( treeiter )
self . refresh_playlist_info ( )
2020-03-26 13:53:36 +03:00
if self . playlist_version == None or not songs == [ ] :
self . refresh_selection ( )
2020-02-25 00:31:39 +03:00
self . playlist_version = self . client . status ( ) [ " playlist " ]
2020-03-22 16:25:04 +03:00
def on_file_changed ( self , * args ) :
2020-03-30 12:54:04 +03:00
self . refresh_selection ( )
2020-02-25 00:31:39 +03:00
2020-03-02 13:49:55 +03:00
def on_disconnected ( self , * args ) :
self . playlist_version = None
2020-05-21 22:18:39 +03:00
class CoverLyricsOSD ( Gtk . Overlay ) :
def __init__ ( self , client , settings , window ) :
Gtk . Overlay . __init__ ( self )
#adding vars
self . client = client
self . settings = settings
self . window = window
#cover
self . cover = MainCover ( self . client , self . settings , self . window )
self . cover . set_property ( " border-width " , 3 )
#lyrics button
2020-05-23 15:09:58 +03:00
self . lyrics_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-view-subtitles-symbolic " , Gtk . IconSize . BUTTON ) )
2020-06-10 23:09:23 +03:00
self . lyrics_button . set_tooltip_text ( _ ( " Show lyrics " ) )
2020-05-21 22:18:39 +03:00
style_context = self . lyrics_button . get_style_context ( )
2020-06-10 23:09:23 +03:00
style_context . add_class ( " circular " )
2020-05-21 22:18:39 +03:00
#revealer
2020-06-10 23:09:23 +03:00
#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 )
2020-05-21 22:18:39 +03:00
#event box
self . event_box = Gtk . EventBox ( )
self . event_box . add ( self . cover )
#packing
self . add ( self . event_box )
2020-06-10 23:09:23 +03:00
self . add_overlay ( revealer )
2020-05-21 22:18:39 +03:00
#connect
self . lyrics_button . connect ( " clicked " , self . on_lyrics_clicked )
self . client . emitter . connect ( " disconnected " , self . on_disconnected )
self . client . emitter . connect ( " reconnected " , self . on_reconnected )
def on_reconnected ( self , * args ) :
2020-06-10 23:09:23 +03:00
self . lyrics_button . set_sensitive ( True )
2020-05-21 22:18:39 +03:00
def on_disconnected ( self , * args ) :
2020-06-10 23:09:23 +03:00
self . lyrics_button . set_sensitive ( False )
2020-05-21 22:18:39 +03:00
self . cover . clear ( )
try :
self . lyrics_win . destroy ( )
except :
pass
def on_lyrics_clicked ( self , widget ) :
2020-06-13 01:03:53 +03:00
self . lyrics_button . set_sensitive ( False )
2020-05-21 22:18:39 +03:00
self . lyrics_win = LyricsWindow ( self . client , self . settings )
2020-06-13 01:03:53 +03:00
def on_destroy ( * args ) :
self . lyrics_button . set_sensitive ( True )
self . lyrics_win . connect ( " destroy " , on_destroy )
2020-05-21 22:18:39 +03:00
self . add_overlay ( self . lyrics_win )
2020-01-11 13:25:15 +03:00
class Browser ( Gtk . Box ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings , window ) :
2020-03-27 19:42:11 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
self . settings = settings
2020-01-18 00:13:58 +03:00
self . window = window
2020-05-21 22:18:39 +03:00
self . use_csd = self . settings . get_boolean ( " use-csd " )
if self . use_csd :
self . icon_size = Gtk . IconSize . BUTTON
else :
self . icon_size = self . settings . get_gtk_icon_size ( " icon-size " )
2020-01-11 13:25:15 +03:00
#widgets
2020-03-29 22:34:51 +03:00
self . back_to_album_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " go-previous-symbolic " , self . icon_size ) )
self . back_to_album_button . set_tooltip_text ( _ ( " Back to current album " ) )
2020-03-25 00:23:45 +03:00
self . search_button = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " system-search-symbolic " , self . icon_size ) )
self . search_button . set_tooltip_text ( _ ( " Search " ) )
2020-03-30 12:54:04 +03:00
self . genre_select = GenreSelect ( self . client , self . settings )
self . artist_view = ArtistView ( self . client , self . settings , self . genre_select )
2020-05-17 21:46:19 +03:00
self . search = SearchWindow ( self . client )
2020-03-30 12:54:04 +03:00
self . album_view = AlbumView ( self . client , self . settings , self . genre_select , self . window )
2020-05-21 22:18:39 +03:00
self . cover = CoverLyricsOSD ( self . client , self . settings , self . window )
2020-03-30 12:54:04 +03:00
self . playlist_view = PlaylistView ( self . client , self . settings )
2020-01-11 13:25:15 +03:00
#connect
2020-03-29 22:34:51 +03:00
self . back_to_album_button . connect ( " clicked " , self . back_to_album )
2020-03-25 00:23:45 +03:00
self . search_button . connect ( " toggled " , self . on_search_toggled )
2020-03-26 01:36:59 +03:00
self . artist_view . connect ( " artists_changed " , self . on_artists_changed )
2020-04-01 19:52:08 +03:00
self . settings . connect ( " changed::playlist-right " , self . on_playlist_pos_settings_changed )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " disconnected " , self . on_disconnected )
self . client . emitter . connect ( " reconnected " , self . on_reconnected )
2020-01-11 13:25:15 +03:00
2020-05-17 21:46:19 +03:00
2020-05-21 22:18:39 +03:00
#packing
2020-05-17 21:46:19 +03:00
self . stack = Gtk . Stack ( )
self . stack . set_transition_type ( 1 )
self . stack . add_named ( self . album_view , " albums " )
self . stack . add_named ( self . search , " search " )
2020-03-25 00:23:45 +03:00
2020-05-18 18:24:09 +03:00
self . paned0 = Gtk . Paned . new ( Gtk . Orientation . HORIZONTAL )
2020-01-11 13:25:15 +03:00
self . paned1 = Gtk . Paned . new ( Gtk . Orientation . HORIZONTAL )
self . paned2 = Gtk . Paned . new ( Gtk . Orientation . HORIZONTAL )
2020-03-25 00:23:45 +03:00
2020-05-21 22:18:39 +03:00
self . paned0 . pack1 ( self . cover , False , False )
self . paned0 . pack2 ( self . playlist_view , True , False )
if self . use_csd :
self . paned1 . pack1 ( self . artist_view , False , False )
else :
hbox = Gtk . Box ( spacing = 6 )
hbox . set_property ( " border-width " , 6 )
hbox . pack_start ( self . back_to_album_button , False , False , 0 )
hbox . pack_start ( self . genre_select , True , True , 0 )
hbox . pack_start ( self . search_button , False , False , 0 )
box1 = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL )
box1 . pack_start ( hbox , False , False , 0 )
box1 . pack_start ( Gtk . Separator . new ( orientation = Gtk . Orientation . HORIZONTAL ) , False , False , 0 )
box1 . pack_start ( self . artist_view , True , True , 0 )
self . paned1 . pack1 ( box1 , False , False )
2020-05-17 21:46:19 +03:00
self . paned1 . pack2 ( self . stack , True , False )
2020-03-25 00:23:45 +03:00
2020-01-11 13:25:15 +03:00
self . paned2 . pack1 ( self . paned1 , True , False )
2020-05-18 18:24:09 +03:00
self . paned2 . pack2 ( self . paned0 , False , False )
2020-03-25 00:23:45 +03:00
2020-01-11 13:25:15 +03:00
self . load_settings ( )
self . pack_start ( self . paned2 , True , True , 0 )
2020-04-01 19:52:08 +03:00
self . on_playlist_pos_settings_changed ( )
2020-03-04 01:43:44 +03:00
2020-01-11 13:25:15 +03:00
def save_settings ( self ) :
2020-05-18 18:24:09 +03:00
self . settings . set_int ( " paned0 " , self . paned0 . get_position ( ) )
2020-01-11 13:25:15 +03:00
self . settings . set_int ( " paned1 " , self . paned1 . get_position ( ) )
self . settings . set_int ( " paned2 " , self . paned2 . get_position ( ) )
2020-03-22 19:05:51 +03:00
self . playlist_view . save_settings ( )
2020-01-11 13:25:15 +03:00
def load_settings ( self ) :
2020-05-18 18:24:09 +03:00
self . paned0 . set_position ( self . settings . get_int ( " paned0 " ) )
2020-01-11 13:25:15 +03:00
self . paned1 . set_position ( self . settings . get_int ( " paned1 " ) )
self . paned2 . set_position ( self . settings . get_int ( " paned2 " ) )
2020-02-25 00:31:39 +03:00
def clear ( self , * args ) :
2020-03-30 13:28:30 +03:00
self . genre_select . clear ( )
2020-03-22 19:05:51 +03:00
self . artist_view . clear ( )
self . album_view . clear ( )
2020-05-21 22:18:39 +03:00
self . search . clear ( )
2020-03-22 19:05:51 +03:00
self . playlist_view . clear ( )
2020-01-11 13:25:15 +03:00
2020-05-17 23:52:10 +03:00
def search_started ( self ) :
return self . search . started ( )
2020-03-29 22:34:51 +03:00
def back_to_album ( self , * args ) :
2020-03-22 19:05:51 +03:00
try : #since this can still be running when the connection is lost, various exceptions can occur
2020-06-04 21:36:34 +03:00
song = ClientHelper . song_to_first_str_dict ( self . client . currentsong ( ) )
2020-03-31 17:07:40 +03:00
try :
artist = song [ self . settings . get_artist_type ( ) ]
except :
try :
artist = song [ " artist " ]
except :
artist = " "
2020-03-26 01:36:59 +03:00
try :
if not song [ ' genre ' ] == self . genre_select . get_value ( ) :
self . genre_select . deactivate ( ) #deactivate genre filter to show all artists
except :
2020-04-07 02:37:53 +03:00
self . genre_select . deactivate ( ) #deactivate genre filter to show all artists
2020-03-26 01:36:59 +03:00
if len ( self . artist_view . get_selected_artists ( ) ) < = 1 :
row_num = len ( self . artist_view . store )
for i in range ( 0 , row_num ) :
path = Gtk . TreePath ( i )
2020-03-31 17:07:40 +03:00
if self . artist_view . store [ path ] [ 0 ] == artist :
2020-03-22 19:05:51 +03:00
self . artist_view . treeview . set_cursor ( path , None , False )
2020-04-03 19:48:17 +03:00
if not self . artist_view . get_selected_artists ( ) == [ artist ] :
self . artist_view . treeview . row_activated ( path , self . artist_view . column_name )
2020-05-17 21:46:19 +03:00
else :
self . search_button . set_active ( False )
2020-05-19 18:23:15 +03:00
self . artist_view . highlight_selected ( )
2020-03-26 01:36:59 +03:00
break
else :
2020-06-01 12:13:11 +03:00
self . search_button . set_active ( False )
2020-03-26 01:36:59 +03:00
self . artist_view . treeview . set_cursor ( Gtk . TreePath ( 0 ) , None , False ) #set cursor to 'all artists'
2020-03-22 19:05:51 +03:00
self . album_view . scroll_to_selected_album ( )
2020-01-11 13:25:15 +03:00
except :
2020-02-01 14:45:34 +03:00
pass
2020-01-11 13:25:15 +03:00
2020-03-25 00:23:45 +03:00
def on_search_toggled ( self , widget ) :
if widget . get_active ( ) :
2020-05-17 21:46:19 +03:00
self . stack . set_visible_child_name ( " search " )
self . search . start ( )
2020-03-25 00:23:45 +03:00
else :
2020-05-17 21:46:19 +03:00
self . stack . set_visible_child_name ( " albums " )
2020-03-25 00:23:45 +03:00
def on_reconnected ( self , * args ) :
2020-03-29 22:34:51 +03:00
self . back_to_album_button . set_sensitive ( True )
2020-03-25 00:23:45 +03:00
self . search_button . set_sensitive ( True )
self . genre_select . set_sensitive ( True )
def on_disconnected ( self , * args ) :
2020-03-30 13:28:30 +03:00
self . clear ( )
2020-03-29 22:34:51 +03:00
self . back_to_album_button . set_sensitive ( False )
2020-03-25 00:23:45 +03:00
self . search_button . set_active ( False )
self . search_button . set_sensitive ( False )
self . genre_select . set_sensitive ( False )
2020-03-26 01:36:59 +03:00
def on_artists_changed ( self , * args ) :
2020-05-17 21:46:19 +03:00
self . search_button . set_active ( False )
2020-03-22 19:05:51 +03:00
artists = self . artist_view . get_selected_artists ( )
self . album_view . refresh ( artists )
2020-01-11 13:25:15 +03:00
2020-04-01 19:52:08 +03:00
def on_playlist_pos_settings_changed ( self , * args ) :
if self . settings . get_boolean ( " playlist-right " ) :
2020-05-18 18:24:09 +03:00
self . paned0 . set_orientation ( Gtk . Orientation . VERTICAL )
2020-03-04 01:43:44 +03:00
self . paned2 . set_orientation ( Gtk . Orientation . HORIZONTAL )
2020-04-01 19:52:08 +03:00
else :
2020-05-18 18:24:09 +03:00
self . paned0 . set_orientation ( Gtk . Orientation . HORIZONTAL )
2020-04-01 19:52:08 +03:00
self . paned2 . set_orientation ( Gtk . Orientation . VERTICAL )
2020-02-29 01:23:53 +03:00
2020-01-11 13:25:15 +03:00
class ProfileSettings ( Gtk . Grid ) :
def __init__ ( self , parent , settings ) :
Gtk . Grid . __init__ ( self )
2020-03-24 18:14:01 +03:00
self . set_row_spacing ( 6 )
self . set_column_spacing ( 12 )
self . set_property ( " border-width " , 18 )
2020-01-11 13:25:15 +03:00
#adding vars
2020-04-09 01:26:21 +03:00
self . settings = settings
2020-01-11 13:25:15 +03:00
#widgets
self . profiles_combo = Gtk . ComboBoxText ( )
self . profiles_combo . set_entry_text_column ( 0 )
add_button = Gtk . Button ( label = None , image = Gtk . Image ( stock = Gtk . STOCK_ADD ) )
delete_button = Gtk . Button ( label = None , image = Gtk . Image ( stock = Gtk . STOCK_DELETE ) )
2020-03-24 18:14:01 +03:00
add_delete_buttons = Gtk . ButtonBox ( )
add_delete_buttons . set_property ( " layout-style " , Gtk . ButtonBoxStyle . EXPAND )
add_delete_buttons . pack_start ( add_button , True , True , 0 )
add_delete_buttons . pack_start ( delete_button , True , True , 0 )
2020-01-11 13:25:15 +03:00
self . profile_entry = Gtk . Entry ( )
self . host_entry = Gtk . Entry ( )
2020-03-10 15:50:36 +03:00
self . port_entry = IntEntry ( 0 , 0 , 65535 , 1 )
2020-03-24 18:14:01 +03:00
address_entry = Gtk . Box ( spacing = 6 )
address_entry . pack_start ( self . host_entry , True , True , 0 )
address_entry . pack_start ( self . port_entry , False , False , 0 )
2020-02-01 15:27:46 +03:00
self . password_entry = Gtk . Entry ( )
self . password_entry . set_visibility ( False )
2020-04-02 11:44:06 +03:00
self . path_entry = Gtk . Entry ( )
2020-04-01 18:03:03 +03:00
self . path_select_button = Gtk . Button ( image = Gtk . Image ( stock = Gtk . STOCK_OPEN ) )
path_box = Gtk . Box ( spacing = 6 )
2020-04-02 11:44:06 +03:00
path_box . pack_start ( self . path_entry , True , True , 0 )
2020-04-01 18:03:03 +03:00
path_box . pack_start ( self . path_select_button , False , False , 0 )
2020-01-11 13:25:15 +03:00
profiles_label = Gtk . Label ( label = _ ( " Profile: " ) )
profiles_label . set_xalign ( 1 )
profile_label = Gtk . Label ( label = _ ( " Name: " ) )
profile_label . set_xalign ( 1 )
host_label = Gtk . Label ( label = _ ( " Host: " ) )
host_label . set_xalign ( 1 )
2020-02-01 15:27:46 +03:00
password_label = Gtk . Label ( label = _ ( " Password: " ) )
password_label . set_xalign ( 1 )
2020-01-11 13:25:15 +03:00
path_label = Gtk . Label ( label = _ ( " Music lib: " ) )
path_label . set_xalign ( 1 )
#connect
2020-02-01 15:27:46 +03:00
self . profile_entry_changed = self . profile_entry . connect ( " changed " , self . on_profile_entry_changed )
self . host_entry_changed = self . host_entry . connect ( " changed " , self . on_host_entry_changed )
2020-01-11 13:25:15 +03:00
self . port_entry_changed = self . port_entry . connect ( " value-changed " , self . on_port_entry_changed )
2020-02-01 15:27:46 +03:00
self . password_entry_changed = self . password_entry . connect ( " changed " , self . on_password_entry_changed )
2020-04-02 11:44:06 +03:00
self . path_entry_changed = self . path_entry . connect ( " changed " , self . on_path_entry_changed )
2020-01-11 13:25:15 +03:00
self . path_select_button . connect ( " clicked " , self . on_path_select_button_clicked , parent )
add_button . connect ( " clicked " , self . on_add_button_clicked )
delete_button . connect ( " clicked " , self . on_delete_button_clicked )
self . profiles_combo_changed = self . profiles_combo . connect ( " changed " , self . on_profiles_changed )
self . profiles_combo_reload ( )
self . profiles_combo . set_active ( 0 )
#packing
self . add ( profiles_label )
self . attach_next_to ( profile_label , profiles_label , Gtk . PositionType . BOTTOM , 1 , 1 )
self . attach_next_to ( host_label , profile_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-03-24 18:14:01 +03:00
self . attach_next_to ( password_label , host_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-02-01 15:27:46 +03:00
self . attach_next_to ( path_label , password_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-03-24 18:14:01 +03:00
self . attach_next_to ( self . profiles_combo , profiles_label , Gtk . PositionType . RIGHT , 2 , 1 )
self . attach_next_to ( add_delete_buttons , self . profiles_combo , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( self . profile_entry , profile_label , Gtk . PositionType . RIGHT , 2 , 1 )
self . attach_next_to ( address_entry , host_label , Gtk . PositionType . RIGHT , 2 , 1 )
self . attach_next_to ( self . password_entry , password_label , Gtk . PositionType . RIGHT , 2 , 1 )
2020-04-01 18:03:03 +03:00
self . attach_next_to ( path_box , path_label , Gtk . PositionType . RIGHT , 2 , 1 )
2020-01-11 13:25:15 +03:00
def profiles_combo_reload ( self , * args ) :
self . profiles_combo . handler_block ( self . profiles_combo_changed )
self . profile_entry . handler_block ( self . profile_entry_changed )
self . host_entry . handler_block ( self . host_entry_changed )
self . port_entry . handler_block ( self . port_entry_changed )
self . profiles_combo . remove_all ( )
for profile in self . settings . get_value ( " profiles " ) :
self . profiles_combo . append_text ( profile )
self . profiles_combo . handler_unblock ( self . profiles_combo_changed )
self . profile_entry . handler_unblock ( self . profile_entry_changed )
self . host_entry . handler_unblock ( self . host_entry_changed )
self . port_entry . handler_unblock ( self . port_entry_changed )
def on_add_button_clicked ( self , * args ) :
pos = self . profiles_combo . get_active ( )
2020-01-28 20:39:18 +03:00
self . settings . array_append ( ' as ' , " profiles " , " new profile " )
self . settings . array_append ( ' as ' , " hosts " , " localhost " )
self . settings . array_append ( ' ai ' , " ports " , 6600 )
2020-02-02 12:38:05 +03:00
self . settings . array_append ( ' as ' , " passwords " , " " )
2020-01-28 20:39:18 +03:00
self . settings . array_append ( ' as ' , " paths " , " " )
2020-01-11 13:25:15 +03:00
self . profiles_combo_reload ( )
self . profiles_combo . set_active ( pos )
def on_delete_button_clicked ( self , * args ) :
pos = self . profiles_combo . get_active ( )
2020-01-28 20:39:18 +03:00
self . settings . array_delete ( ' as ' , " profiles " , pos )
self . settings . array_delete ( ' as ' , " hosts " , pos )
self . settings . array_delete ( ' ai ' , " ports " , pos )
2020-02-02 12:38:05 +03:00
self . settings . array_delete ( ' as ' , " passwords " , pos )
2020-01-28 20:39:18 +03:00
self . settings . array_delete ( ' as ' , " paths " , pos )
2020-02-02 16:20:25 +03:00
if len ( self . settings . get_value ( " profiles " ) ) == 0 :
self . on_add_button_clicked ( )
else :
self . profiles_combo_reload ( )
self . profiles_combo . set_active ( 0 )
2020-01-11 13:25:15 +03:00
def on_profile_entry_changed ( self , * args ) :
pos = self . profiles_combo . get_active ( )
2020-01-28 20:39:18 +03:00
self . settings . array_modify ( ' as ' , " profiles " , pos , self . profile_entry . get_text ( ) )
2020-01-11 13:25:15 +03:00
self . profiles_combo_reload ( )
self . profiles_combo . set_active ( pos )
def on_host_entry_changed ( self , * args ) :
2020-01-28 20:39:18 +03:00
self . settings . array_modify ( ' as ' , " hosts " , self . profiles_combo . get_active ( ) , self . host_entry . get_text ( ) )
2020-01-11 13:25:15 +03:00
def on_port_entry_changed ( self , * args ) :
2020-01-28 20:39:18 +03:00
self . settings . array_modify ( ' ai ' , " ports " , self . profiles_combo . get_active ( ) , self . port_entry . get_int ( ) )
2020-01-11 13:25:15 +03:00
2020-02-01 15:27:46 +03:00
def on_password_entry_changed ( self , * args ) :
self . settings . array_modify ( ' as ' , " passwords " , self . profiles_combo . get_active ( ) , self . password_entry . get_text ( ) )
2020-04-02 11:44:06 +03:00
def on_path_entry_changed ( self , * args ) :
self . settings . array_modify ( ' as ' , " paths " , self . profiles_combo . get_active ( ) , self . path_entry . get_text ( ) )
2020-01-11 13:25:15 +03:00
def on_path_select_button_clicked ( self , widget , parent ) :
2020-04-09 01:26:21 +03:00
dialog = Gtk . FileChooserDialog ( title = _ ( " Choose directory " ) , transient_for = parent , action = Gtk . FileChooserAction . SELECT_FOLDER )
2020-01-11 13:25:15 +03:00
dialog . add_buttons ( Gtk . STOCK_CANCEL , Gtk . ResponseType . CANCEL )
dialog . add_buttons ( Gtk . STOCK_OK , Gtk . ResponseType . OK )
dialog . set_default_size ( 800 , 400 )
dialog . set_current_folder ( self . settings . get_value ( " paths " ) [ self . profiles_combo . get_active ( ) ] )
2020-04-09 01:26:21 +03:00
response = dialog . run ( )
2020-01-11 13:25:15 +03:00
if response == Gtk . ResponseType . OK :
2020-01-28 20:39:18 +03:00
self . settings . array_modify ( ' as ' , " paths " , self . profiles_combo . get_active ( ) , dialog . get_filename ( ) )
2020-04-02 11:44:06 +03:00
self . path_entry . set_text ( dialog . get_filename ( ) )
2020-01-11 13:25:15 +03:00
dialog . destroy ( )
def on_profiles_changed ( self , * args ) :
active = self . profiles_combo . get_active ( )
self . profile_entry . handler_block ( self . profile_entry_changed )
self . host_entry . handler_block ( self . host_entry_changed )
self . port_entry . handler_block ( self . port_entry_changed )
2020-02-01 15:27:46 +03:00
self . password_entry . handler_block ( self . password_entry_changed )
2020-01-11 13:25:15 +03:00
self . profile_entry . set_text ( self . settings . get_value ( " profiles " ) [ active ] )
self . host_entry . set_text ( self . settings . get_value ( " hosts " ) [ active ] )
self . port_entry . set_int ( self . settings . get_value ( " ports " ) [ active ] )
2020-02-01 15:27:46 +03:00
self . password_entry . set_text ( self . settings . get_value ( " passwords " ) [ active ] )
2020-04-02 11:44:06 +03:00
self . path_entry . set_text ( self . settings . get_value ( " paths " ) [ active ] )
2020-01-11 13:25:15 +03:00
self . profile_entry . handler_unblock ( self . profile_entry_changed )
self . host_entry . handler_unblock ( self . host_entry_changed )
self . port_entry . handler_unblock ( self . port_entry_changed )
2020-02-01 15:27:46 +03:00
self . password_entry . handler_unblock ( self . password_entry_changed )
2020-01-11 13:25:15 +03:00
2020-03-24 18:14:01 +03:00
class GeneralSettings ( Gtk . Box ) :
2020-01-11 13:25:15 +03:00
def __init__ ( self , settings ) :
2020-03-24 18:14:01 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL , spacing = 6 )
self . set_property ( " border-width " , 18 )
2020-01-11 13:25:15 +03:00
#adding vars
2020-04-09 01:26:21 +03:00
self . settings = settings
2020-01-11 13:25:15 +03:00
#widgets
track_cover_label = Gtk . Label ( label = _ ( " Main cover size: " ) )
2020-03-25 00:23:45 +03:00
track_cover_label . set_xalign ( 0 )
2020-03-24 18:14:01 +03:00
track_cover_size = IntEntry ( self . settings . get_int ( " track-cover " ) , 100 , 1200 , 10 )
2020-03-22 23:49:55 +03:00
album_cover_label = Gtk . Label ( label = _ ( " Album view cover size: " ) )
2020-03-25 00:23:45 +03:00
album_cover_label . set_xalign ( 0 )
2020-03-10 15:50:36 +03:00
album_cover_size = IntEntry ( self . settings . get_int ( " album-cover " ) , 50 , 600 , 10 )
2020-01-11 13:25:15 +03:00
2020-03-25 00:23:45 +03:00
icon_size_label1 = Gtk . Label ( label = _ ( " Button icon size: " ) )
icon_size_label1 . set_xalign ( 0 )
icon_size_label2 = Gtk . Label ( label = _ ( " (restart required) " ) )
icon_size_label2 . set_xalign ( 0 )
icon_size_label2 . set_sensitive ( False )
2020-01-28 21:59:14 +03:00
icon_size_combo = Gtk . ComboBoxText ( )
icon_size_combo . set_entry_text_column ( 0 )
2020-01-29 18:42:45 +03:00
sizes = [ 16 , 24 , 32 , 48 ]
2020-01-28 21:59:14 +03:00
for i in sizes :
icon_size_combo . append_text ( str ( i ) )
icon_size_combo . set_active ( sizes . index ( self . settings . get_int ( " icon-size " ) ) )
2020-04-01 19:52:08 +03:00
combo_settings = { }
combo_settings_data = [ ( _ ( " Sort albums by: " ) , _ ( " name " ) , _ ( " year " ) , " sort-albums-by-year " ) , \
( _ ( " Position of playlist: " ) , _ ( " bottom " ) , _ ( " right " ) , " playlist-right " ) ]
for data in combo_settings_data :
combo_settings [ data [ 3 ] ] = ( Gtk . Label ( ) , Gtk . ComboBoxText ( ) )
combo_settings [ data [ 3 ] ] [ 0 ] . set_label ( data [ 0 ] )
combo_settings [ data [ 3 ] ] [ 0 ] . set_xalign ( 0 )
combo_settings [ data [ 3 ] ] [ 1 ] . set_entry_text_column ( 0 )
combo_settings [ data [ 3 ] ] [ 1 ] . append_text ( data [ 1 ] )
combo_settings [ data [ 3 ] ] [ 1 ] . append_text ( data [ 2 ] )
if self . settings . get_boolean ( data [ 3 ] ) :
combo_settings [ data [ 3 ] ] [ 1 ] . set_active ( 1 )
else :
combo_settings [ data [ 3 ] ] [ 1 ] . set_active ( 0 )
combo_settings [ data [ 3 ] ] [ 1 ] . connect ( " changed " , self . on_combo_changed , data [ 3 ] )
2020-03-24 18:14:01 +03:00
#headings
view_heading = Gtk . Label ( )
view_heading . set_markup ( _ ( " <b>View</b> " ) )
view_heading . set_xalign ( 0 )
behavior_heading = Gtk . Label ( )
behavior_heading . set_markup ( _ ( " <b>Behavior</b> " ) )
behavior_heading . set_xalign ( 0 )
2020-01-11 13:25:15 +03:00
2020-04-01 13:51:40 +03:00
#check buttons
2020-03-24 18:14:01 +03:00
check_buttons = { }
2020-05-21 22:18:39 +03:00
settings_list = [ ( _ ( " Use Client-side decoration " ) , " use-csd " ) , \
( _ ( " Show stop button " ) , " show-stop " ) , \
2020-03-22 23:49:55 +03:00
( _ ( " Show initials in artist view " ) , " show-initials " ) , \
2020-03-12 19:09:24 +03:00
( _ ( " Show tooltips in album view " ) , " show-album-view-tooltips " ) , \
2020-03-31 17:07:40 +03:00
( _ ( " Use ' Album Artist ' tag " ) , " use-album-artist " ) , \
2020-03-22 23:49:55 +03:00
( _ ( " Send notification on title change " ) , " send-notify " ) , \
( _ ( " Stop playback on quit " ) , " stop-on-quit " ) , \
2020-04-01 14:22:50 +03:00
( _ ( " Play selected albums and titles immediately " ) , " force-mode " ) ]
2020-01-11 13:25:15 +03:00
2020-03-06 13:27:05 +03:00
for data in settings_list :
2020-03-24 18:14:01 +03:00
check_buttons [ data [ 1 ] ] = Gtk . CheckButton ( label = data [ 0 ] )
check_buttons [ data [ 1 ] ] . set_active ( self . settings . get_boolean ( data [ 1 ] ) )
check_buttons [ data [ 1 ] ] . connect ( " toggled " , self . on_toggled , data [ 1 ] )
check_buttons [ data [ 1 ] ] . set_margin_start ( 12 )
2020-01-11 13:25:15 +03:00
2020-04-01 13:51:40 +03:00
#view grid
view_grid = Gtk . Grid ( )
view_grid . set_row_spacing ( 6 )
view_grid . set_column_spacing ( 12 )
view_grid . set_margin_start ( 12 )
view_grid . add ( track_cover_label )
view_grid . attach_next_to ( album_cover_label , track_cover_label , Gtk . PositionType . BOTTOM , 1 , 1 )
view_grid . attach_next_to ( icon_size_label1 , album_cover_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-04-01 19:52:08 +03:00
view_grid . attach_next_to ( combo_settings [ " playlist-right " ] [ 0 ] , icon_size_label1 , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-04-01 13:51:40 +03:00
view_grid . attach_next_to ( track_cover_size , track_cover_label , Gtk . PositionType . RIGHT , 1 , 1 )
view_grid . attach_next_to ( album_cover_size , album_cover_label , Gtk . PositionType . RIGHT , 1 , 1 )
view_grid . attach_next_to ( icon_size_combo , icon_size_label1 , Gtk . PositionType . RIGHT , 1 , 1 )
view_grid . attach_next_to ( icon_size_label2 , icon_size_combo , Gtk . PositionType . RIGHT , 1 , 1 )
2020-04-01 19:52:08 +03:00
view_grid . attach_next_to ( combo_settings [ " playlist-right " ] [ 1 ] , combo_settings [ " playlist-right " ] [ 0 ] , Gtk . PositionType . RIGHT , 1 , 1 )
2020-04-01 13:51:40 +03:00
#behavior grid
behavior_grid = Gtk . Grid ( )
behavior_grid . set_row_spacing ( 6 )
behavior_grid . set_column_spacing ( 12 )
behavior_grid . set_margin_start ( 12 )
2020-04-01 19:52:08 +03:00
behavior_grid . add ( combo_settings [ " sort-albums-by-year " ] [ 0 ] )
behavior_grid . attach_next_to ( combo_settings [ " sort-albums-by-year " ] [ 1 ] , combo_settings [ " sort-albums-by-year " ] [ 0 ] , Gtk . PositionType . RIGHT , 1 , 1 )
2020-04-01 13:51:40 +03:00
2020-01-11 13:25:15 +03:00
#connect
track_cover_size . connect ( " value-changed " , self . on_int_changed , " track-cover " )
album_cover_size . connect ( " value-changed " , self . on_int_changed , " album-cover " )
2020-01-28 21:59:14 +03:00
icon_size_combo . connect ( " changed " , self . on_icon_size_changed )
2020-01-11 13:25:15 +03:00
#packing
2020-05-21 23:28:58 +03:00
restart_label = Gtk . Label ( label = _ ( " (restart required) " ) )
restart_label . set_xalign ( 0 )
restart_label . set_sensitive ( False )
box = Gtk . Box ( spacing = 12 )
box . pack_start ( check_buttons [ " use-csd " ] , False , False , 0 )
box . pack_start ( restart_label , False , False , 0 )
2020-03-24 18:14:01 +03:00
self . pack_start ( view_heading , True , True , 0 )
2020-05-21 23:28:58 +03:00
self . pack_start ( box , True , True , 0 )
2020-03-24 18:14:01 +03:00
self . pack_start ( check_buttons [ " show-stop " ] , True , True , 0 )
self . pack_start ( check_buttons [ " show-initials " ] , True , True , 0 )
self . pack_start ( check_buttons [ " show-album-view-tooltips " ] , True , True , 0 )
2020-04-01 13:51:40 +03:00
self . pack_start ( view_grid , True , True , 0 )
2020-03-24 18:14:01 +03:00
self . pack_start ( behavior_heading , True , True , 0 )
2020-03-31 17:07:40 +03:00
self . pack_start ( check_buttons [ " use-album-artist " ] , True , True , 0 )
2020-03-24 18:14:01 +03:00
self . pack_start ( check_buttons [ " send-notify " ] , True , True , 0 )
self . pack_start ( check_buttons [ " stop-on-quit " ] , True , True , 0 )
2020-03-30 11:48:31 +03:00
self . pack_start ( check_buttons [ " force-mode " ] , True , True , 0 )
2020-04-01 13:51:40 +03:00
self . pack_start ( behavior_grid , True , True , 0 )
2020-01-11 13:25:15 +03:00
def on_int_changed ( self , widget , key ) :
self . settings . set_int ( key , widget . get_int ( ) )
2020-01-28 21:59:14 +03:00
def on_icon_size_changed ( self , box ) :
active_size = int ( box . get_active_text ( ) )
self . settings . set_int ( " icon-size " , active_size )
2020-04-01 19:52:08 +03:00
def on_combo_changed ( self , box , key ) :
2020-04-01 13:51:40 +03:00
active = box . get_active ( )
if active == 0 :
2020-04-01 19:52:08 +03:00
self . settings . set_boolean ( key , False )
2020-04-01 13:51:40 +03:00
else :
2020-04-01 19:52:08 +03:00
self . settings . set_boolean ( key , True )
2020-04-01 13:51:40 +03:00
2020-03-24 18:14:01 +03:00
def on_toggled ( self , widget , key ) :
self . settings . set_boolean ( key , widget . get_active ( ) )
2020-03-06 13:27:05 +03:00
2020-03-04 18:39:59 +03:00
class PlaylistSettings ( Gtk . Box ) :
def __init__ ( self , settings ) :
2020-03-24 18:14:01 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL , spacing = 6 )
self . set_property ( " border-width " , 18 )
2020-03-04 18:39:59 +03:00
#adding vars
2020-04-09 01:26:21 +03:00
self . settings = settings
2020-03-04 18:39:59 +03:00
2020-03-06 11:38:05 +03:00
#label
2020-03-24 18:14:01 +03:00
label = Gtk . Label ( label = _ ( " Choose the order of information to appear in the playlist: " ) )
2020-03-06 11:38:05 +03:00
label . set_line_wrap ( True )
2020-03-24 18:14:01 +03:00
label . set_xalign ( 0 )
2020-03-06 11:38:05 +03:00
2020-03-04 18:39:59 +03:00
#Store
#(toggle, header, index)
2020-04-09 01:26:21 +03:00
self . store = Gtk . ListStore ( bool , str , int )
2020-03-04 18:39:59 +03:00
#TreeView
2020-04-09 01:26:21 +03:00
self . treeview = Gtk . TreeView ( model = self . store )
2020-03-04 18:39:59 +03:00
self . treeview . set_search_column ( - 1 )
self . treeview . set_reorderable ( True )
2020-03-24 18:14:01 +03:00
self . treeview . set_headers_visible ( False )
#selection
2020-04-09 01:26:21 +03:00
self . selection = self . treeview . get_selection ( )
2020-03-04 18:39:59 +03:00
#Column
2020-04-09 01:26:21 +03:00
renderer_text = Gtk . CellRendererText ( )
renderer_toggle = Gtk . CellRendererToggle ( )
2020-03-04 18:39:59 +03:00
column_toggle = Gtk . TreeViewColumn ( " " , renderer_toggle , active = 0 )
self . treeview . append_column ( column_toggle )
2020-03-24 18:14:01 +03:00
column_text = Gtk . TreeViewColumn ( " " , renderer_text , text = 1 )
2020-03-04 18:39:59 +03:00
self . treeview . append_column ( column_text )
2020-03-06 13:27:05 +03:00
#fill store
2020-03-04 18:39:59 +03:00
self . headers = [ _ ( " No " ) , _ ( " Disc " ) , _ ( " Title " ) , _ ( " Artist " ) , _ ( " Album " ) , _ ( " Length " ) , _ ( " Year " ) , _ ( " Genre " ) ]
visibilities = self . settings . get_value ( " column-visibilities " ) . unpack ( )
for index in self . settings . get_value ( " column-permutation " ) :
self . store . append ( [ visibilities [ index ] , self . headers [ index ] , index ] )
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . treeview )
2020-03-24 18:14:01 +03:00
frame = Gtk . Frame ( )
frame . add ( scroll )
#Toolbar
toolbar = Gtk . Toolbar ( )
style_context = toolbar . get_style_context ( )
style_context . add_class ( " inline-toolbar " )
self . up_button = Gtk . ToolButton . new ( Gtk . Image . new_from_icon_name ( " go-up-symbolic " , Gtk . IconSize . SMALL_TOOLBAR ) )
self . up_button . set_sensitive ( False )
self . down_button = Gtk . ToolButton . new ( Gtk . Image . new_from_icon_name ( " go-down-symbolic " , Gtk . IconSize . SMALL_TOOLBAR ) )
self . down_button . set_sensitive ( False )
toolbar . insert ( self . up_button , 0 )
toolbar . insert ( self . down_button , 1 )
#column chooser
column_chooser = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL )
column_chooser . pack_start ( frame , True , True , 0 )
column_chooser . pack_start ( toolbar , False , False , 0 )
2020-03-04 18:39:59 +03:00
2020-03-24 18:14:01 +03:00
#connect
self . store . connect ( " row-deleted " , self . save_permutation )
renderer_toggle . connect ( " toggled " , self . on_cell_toggled )
self . up_button . connect ( " clicked " , self . on_up_button_clicked )
self . down_button . connect ( " clicked " , self . on_down_button_clicked )
self . selection . connect ( " changed " , self . set_button_sensitivity )
#packing
2020-03-06 11:38:05 +03:00
self . pack_start ( label , False , False , 0 )
2020-03-24 18:14:01 +03:00
self . pack_start ( column_chooser , True , True , 0 )
2020-03-04 18:39:59 +03:00
def on_cell_toggled ( self , widget , path ) :
2020-04-09 01:26:21 +03:00
self . store [ path ] [ 0 ] = not self . store [ path ] [ 0 ]
2020-03-04 18:39:59 +03:00
self . settings . array_modify ( ' ab ' , " column-visibilities " , self . store [ path ] [ 2 ] , self . store [ path ] [ 0 ] )
2020-03-24 18:14:01 +03:00
def save_permutation ( self , * args ) :
2020-03-04 18:39:59 +03:00
permutation = [ ]
for row in self . store :
permutation . append ( row [ 2 ] )
self . settings . set_value ( " column-permutation " , GLib . Variant ( " ai " , permutation ) )
2020-03-24 18:14:01 +03:00
def on_up_button_clicked ( self , * args ) :
treeiter = self . selection . get_selected ( ) [ 1 ]
path = self . store . get_path ( treeiter )
path . prev ( )
prev = self . store . get_iter ( path )
self . store . move_before ( treeiter , prev )
self . set_button_sensitivity ( )
self . save_permutation ( )
def on_down_button_clicked ( self , * args ) :
treeiter = self . selection . get_selected ( ) [ 1 ]
path = self . store . get_path ( treeiter )
next = self . store . iter_next ( treeiter )
self . store . move_after ( treeiter , next )
self . set_button_sensitivity ( )
self . save_permutation ( )
def set_button_sensitivity ( self , * args ) :
treeiter = self . selection . get_selected ( ) [ 1 ]
path = self . store . get_path ( treeiter )
if treeiter == None :
self . up_button . set_sensitive ( False )
self . down_button . set_sensitive ( False )
elif self . store . iter_next ( treeiter ) == None :
self . up_button . set_sensitive ( True )
self . down_button . set_sensitive ( False )
elif not path . prev ( ) :
self . up_button . set_sensitive ( False )
self . down_button . set_sensitive ( True )
else :
self . up_button . set_sensitive ( True )
self . down_button . set_sensitive ( True )
2020-01-11 13:25:15 +03:00
class SettingsDialog ( Gtk . Dialog ) :
def __init__ ( self , parent , settings ) :
Gtk . Dialog . __init__ ( self , title = _ ( " Settings " ) , transient_for = parent )
self . add_button ( Gtk . STOCK_OK , Gtk . ResponseType . OK )
self . set_default_size ( 500 , 400 )
#adding vars
2020-04-09 01:26:21 +03:00
self . settings = settings
2020-01-11 13:25:15 +03:00
#widgets
general = GeneralSettings ( self . settings )
profiles = ProfileSettings ( parent , self . settings )
2020-03-04 18:39:59 +03:00
playlist = PlaylistSettings ( self . settings )
2020-01-11 13:25:15 +03:00
#packing
2020-04-09 01:26:21 +03:00
tabs = Gtk . Notebook ( )
2020-01-11 13:25:15 +03:00
tabs . append_page ( general , Gtk . Label ( label = _ ( " General " ) ) )
tabs . append_page ( profiles , Gtk . Label ( label = _ ( " Profiles " ) ) )
2020-03-04 18:39:59 +03:00
tabs . append_page ( playlist , Gtk . Label ( label = _ ( " Playlist " ) ) )
2020-01-11 13:25:15 +03:00
self . vbox . pack_start ( tabs , True , True , 0 ) #vbox default widget of dialogs
2020-05-21 22:18:39 +03:00
self . vbox . set_spacing ( 3 )
2020-01-11 13:25:15 +03:00
self . show_all ( )
class ClientControl ( Gtk . ButtonBox ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings ) :
2020-03-24 18:14:01 +03:00
Gtk . ButtonBox . __init__ ( self , spacing = 6 )
self . set_property ( " layout-style " , Gtk . ButtonBoxStyle . EXPAND )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
self . settings = settings
2020-01-28 21:59:14 +03:00
self . icon_size = self . settings . get_gtk_icon_size ( " icon-size " )
2020-01-11 13:25:15 +03:00
#widgets
2020-04-09 01:26:21 +03:00
self . play_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-playback-start-symbolic " , self . icon_size ) )
self . stop_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-playback-stop-symbolic " , self . icon_size ) )
self . prev_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-skip-backward-symbolic " , self . icon_size ) )
self . next_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-skip-forward-symbolic " , self . icon_size ) )
2020-01-11 13:25:15 +03:00
#connect
self . play_button . connect ( " clicked " , self . on_play_clicked )
self . stop_button . connect ( " clicked " , self . on_stop_clicked )
self . prev_button . connect ( " clicked " , self . on_prev_clicked )
self . next_button . connect ( " clicked " , self . on_next_clicked )
self . settings . connect ( " changed::show-stop " , self . on_settings_changed )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " player " , self . refresh )
2020-01-11 13:25:15 +03:00
#packing
self . pack_start ( self . prev_button , True , True , 0 )
self . pack_start ( self . play_button , True , True , 0 )
if self . settings . get_boolean ( " show-stop " ) :
self . pack_start ( self . stop_button , True , True , 0 )
self . pack_start ( self . next_button , True , True , 0 )
2020-03-22 19:05:51 +03:00
def refresh ( self , * args ) :
2020-02-25 00:31:39 +03:00
status = self . client . status ( )
if status [ " state " ] == " play " :
self . play_button . set_image ( Gtk . Image . new_from_icon_name ( " media-playback-pause-symbolic " , self . icon_size ) )
self . prev_button . set_sensitive ( True )
self . next_button . set_sensitive ( True )
elif status [ " state " ] == " pause " :
self . play_button . set_image ( Gtk . Image . new_from_icon_name ( " media-playback-start-symbolic " , self . icon_size ) )
self . prev_button . set_sensitive ( True )
self . next_button . set_sensitive ( True )
else :
self . play_button . set_image ( Gtk . Image . new_from_icon_name ( " media-playback-start-symbolic " , self . icon_size ) )
self . prev_button . set_sensitive ( False )
self . next_button . set_sensitive ( False )
2020-01-11 13:25:15 +03:00
def on_play_clicked ( self , widget ) :
if self . client . connected ( ) :
status = self . client . status ( )
if status [ " state " ] == " play " :
self . client . pause ( 1 )
elif status [ " state " ] == " pause " :
self . client . pause ( 0 )
else :
try :
self . client . play ( status [ " song " ] )
except :
try :
2020-01-26 23:32:12 +03:00
self . client . play ( )
2020-01-11 13:25:15 +03:00
except :
pass
def on_stop_clicked ( self , widget ) :
if self . client . connected ( ) :
self . client . stop ( )
def on_prev_clicked ( self , widget ) :
if self . client . connected ( ) :
self . client . previous ( )
def on_next_clicked ( self , widget ) :
if self . client . connected ( ) :
self . client . next ( )
def on_settings_changed ( self , * args ) :
if self . settings . get_boolean ( " show-stop " ) :
self . pack_start ( self . stop_button , True , True , 0 )
self . reorder_child ( self . stop_button , 2 )
self . stop_button . show ( )
else :
self . remove ( self . stop_button )
class SeekBar ( Gtk . Box ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client ) :
2020-01-11 13:25:15 +03:00
Gtk . Box . __init__ ( self )
2020-03-24 18:14:01 +03:00
self . set_hexpand ( True )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-02-16 14:20:38 +03:00
self . seek_time = " 10 " #seek increment in seconds
2020-03-28 15:23:56 +03:00
self . update = True
self . jumped = False
2020-01-11 13:25:15 +03:00
2020-02-16 14:20:38 +03:00
#labels
2020-01-11 13:25:15 +03:00
self . elapsed = Gtk . Label ( )
2020-05-14 17:03:34 +03:00
self . elapsed . set_width_chars ( 5 )
2020-01-11 13:25:15 +03:00
self . rest = Gtk . Label ( )
2020-05-14 17:03:34 +03:00
self . rest . set_width_chars ( 6 )
2020-02-16 14:20:38 +03:00
#progress bar
2020-01-11 13:25:15 +03:00
self . scale = Gtk . Scale . new_with_range ( orientation = Gtk . Orientation . HORIZONTAL , min = 0 , max = 100 , step = 0.001 )
2020-03-28 15:23:56 +03:00
self . scale . set_show_fill_level ( True )
self . scale . set_restrict_to_fill_level ( False )
2020-01-11 13:25:15 +03:00
self . scale . set_draw_value ( False )
2020-03-28 15:23:56 +03:00
#css (scale)
style_context = self . scale . get_style_context ( )
2020-04-09 01:26:21 +03:00
provider = Gtk . CssProvider ( )
css = b """ scale fill { background-color: @theme_selected_bg_color; } """
2020-03-28 15:23:56 +03:00
provider . load_from_data ( css )
style_context . add_provider ( provider , 800 )
2020-02-16 14:20:38 +03:00
#event boxes
self . elapsed_event_box = Gtk . EventBox ( )
self . rest_event_box = Gtk . EventBox ( )
2020-01-11 13:25:15 +03:00
#connect
2020-03-22 19:05:51 +03:00
self . elapsed_event_box . connect ( " button-press-event " , self . on_elapsed_button_press_event )
self . rest_event_box . connect ( " button-press-event " , self . on_rest_button_press_event )
2020-03-28 15:23:56 +03:00
self . scale . connect ( " change-value " , self . on_change_value )
self . scale . connect ( " scroll-event " , self . dummy ) #disable mouse wheel
self . scale . connect ( " button-press-event " , self . on_scale_button_press_event )
self . scale . connect ( " button-release-event " , self . on_scale_button_release_event )
2020-05-16 10:55:11 +03:00
self . client . emitter . connect ( " disconnected " , self . disable )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " player " , self . on_player )
2020-05-16 10:42:27 +03:00
#periodic_signal
self . periodic_signal = self . client . emitter . connect ( " periodic_signal " , self . refresh )
2020-01-11 13:25:15 +03:00
#packing
2020-02-16 14:20:38 +03:00
self . elapsed_event_box . add ( self . elapsed )
self . rest_event_box . add ( self . rest )
self . pack_start ( self . elapsed_event_box , False , False , 0 )
2020-01-11 13:25:15 +03:00
self . pack_start ( self . scale , True , True , 0 )
2020-02-16 14:20:38 +03:00
self . pack_end ( self . rest_event_box , False , False , 0 )
2020-01-11 13:25:15 +03:00
2020-05-16 10:42:27 +03:00
self . disable ( )
2020-03-01 01:57:44 +03:00
def dummy ( self , * args ) :
return True
2020-03-28 15:23:56 +03:00
def on_scale_button_press_event ( self , widget , event ) :
2020-03-30 21:44:28 +03:00
if event . button == 1 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-28 15:23:56 +03:00
self . update = False
self . scale . set_has_origin ( False )
2020-03-30 21:44:28 +03:00
if event . button == 3 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-28 15:23:56 +03:00
self . jumped = False
def on_scale_button_release_event ( self , widget , event ) :
if event . button == 1 :
if self . jumped : #actual seek
status = self . client . status ( )
duration = float ( status [ " duration " ] )
factor = ( self . scale . get_value ( ) / 100 )
pos = ( duration * factor )
self . client . seekcur ( pos )
self . jumped = False
self . scale . set_has_origin ( True )
self . update = True
2020-05-16 13:42:10 +03:00
self . refresh ( )
2020-03-28 15:23:56 +03:00
def on_change_value ( self , range , scroll , value ) : #value is inaccurate
2020-03-27 20:16:50 +03:00
if scroll == Gtk . ScrollType . STEP_BACKWARD :
self . seek_backward ( )
elif scroll == Gtk . ScrollType . STEP_FORWARD :
self . seek_forward ( )
elif scroll == Gtk . ScrollType . JUMP :
status = self . client . status ( )
duration = float ( status [ " duration " ] )
factor = ( value / 100 )
2020-03-28 15:23:56 +03:00
if factor > 1 : #fix display error
2020-03-27 20:16:50 +03:00
factor = 1
2020-03-28 15:23:56 +03:00
elapsed = ( factor * duration )
2020-05-14 17:03:34 +03:00
self . elapsed . set_text ( str ( datetime . timedelta ( seconds = int ( elapsed ) ) ) . lstrip ( " 0 " ) . lstrip ( " : " ) )
self . rest . set_text ( " - " + str ( datetime . timedelta ( seconds = int ( duration - elapsed ) ) ) . lstrip ( " 0 " ) . lstrip ( " : " ) )
2020-03-28 15:23:56 +03:00
self . jumped = True
2020-01-11 13:25:15 +03:00
2020-03-03 18:11:30 +03:00
def seek_forward ( self ) :
self . client . seekcur ( " + " + self . seek_time )
def seek_backward ( self ) :
self . client . seekcur ( " - " + self . seek_time )
2020-05-16 10:55:11 +03:00
def enable ( self , * args ) :
2020-03-25 21:10:46 +03:00
self . scale . set_sensitive ( True )
self . scale . set_range ( 0 , 100 )
self . elapsed_event_box . set_sensitive ( True )
self . rest_event_box . set_sensitive ( True )
2020-05-16 10:55:11 +03:00
def disable ( self , * args ) :
2020-03-25 21:10:46 +03:00
self . scale . set_sensitive ( False )
self . scale . set_range ( 0 , 0 )
self . elapsed_event_box . set_sensitive ( False )
self . rest_event_box . set_sensitive ( False )
2020-05-14 17:03:34 +03:00
self . elapsed . set_text ( " 00:00 " )
self . rest . set_text ( " -00:00 " )
2020-03-25 21:10:46 +03:00
2020-02-16 14:20:38 +03:00
def on_elapsed_button_press_event ( self , widget , event ) :
2020-03-30 21:44:28 +03:00
if event . button == 1 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 18:11:30 +03:00
self . seek_backward ( )
2020-03-30 21:44:28 +03:00
elif event . button == 3 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 18:11:30 +03:00
self . seek_forward ( )
2020-02-16 14:20:38 +03:00
def on_rest_button_press_event ( self , widget , event ) :
2020-03-30 21:44:28 +03:00
if event . button == 1 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 18:11:30 +03:00
self . seek_forward ( )
2020-03-30 21:44:28 +03:00
elif event . button == 3 and event . type == Gdk . EventType . BUTTON_PRESS :
2020-03-03 18:11:30 +03:00
self . seek_backward ( )
2020-02-16 14:20:38 +03:00
2020-03-25 21:10:46 +03:00
def on_player ( self , * args ) :
status = self . client . status ( )
if status [ ' state ' ] == " stop " :
self . disable ( )
2020-05-16 10:42:27 +03:00
elif status [ ' state ' ] == " pause " : #needed for seeking in paused state
self . enable ( )
2020-03-25 21:10:46 +03:00
self . refresh ( )
else :
self . enable ( )
2020-05-16 10:42:27 +03:00
def refresh ( self , * args ) :
2020-01-11 13:25:15 +03:00
try :
status = self . client . status ( )
duration = float ( status [ " duration " ] )
elapsed = float ( status [ " elapsed " ] )
2020-03-28 15:23:56 +03:00
if elapsed > duration : #fix display error
2020-03-25 21:10:46 +03:00
elapsed = duration
2020-01-11 13:25:15 +03:00
fraction = ( elapsed / duration ) * 100
2020-03-28 15:23:56 +03:00
if self . update :
self . scale . set_value ( fraction )
2020-05-14 17:03:34 +03:00
self . elapsed . set_text ( str ( datetime . timedelta ( seconds = int ( elapsed ) ) ) . lstrip ( " 0 " ) . lstrip ( " : " ) )
self . rest . set_text ( " - " + str ( datetime . timedelta ( seconds = int ( duration - elapsed ) ) ) . lstrip ( " 0 " ) . lstrip ( " : " ) )
2020-03-28 15:23:56 +03:00
self . scale . set_fill_level ( fraction )
2020-01-11 13:25:15 +03:00
except :
2020-03-25 21:10:46 +03:00
self . disable ( )
2020-01-11 13:25:15 +03:00
class PlaybackOptions ( Gtk . Box ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings ) :
2020-03-24 18:14:01 +03:00
Gtk . Box . __init__ ( self , spacing = 6 )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-01-28 21:59:14 +03:00
self . settings = settings
self . icon_size = self . settings . get_gtk_icon_size ( " icon-size " )
2020-01-11 13:25:15 +03:00
#widgets
2020-01-28 21:59:14 +03:00
self . random = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " media-playlist-shuffle-symbolic " , self . icon_size ) )
2020-01-11 13:25:15 +03:00
self . random . set_tooltip_text ( _ ( " Random mode " ) )
2020-01-28 21:59:14 +03:00
self . repeat = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " media-playlist-repeat-symbolic " , self . icon_size ) )
2020-01-11 13:25:15 +03:00
self . repeat . set_tooltip_text ( _ ( " Repeat mode " ) )
2020-01-28 21:59:14 +03:00
self . single = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " zoom-original-symbolic " , self . icon_size ) )
2020-01-11 13:25:15 +03:00
self . single . set_tooltip_text ( _ ( " Single mode " ) )
2020-01-28 21:59:14 +03:00
self . consume = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " edit-cut-symbolic " , self . icon_size ) )
2020-01-11 13:25:15 +03:00
self . consume . set_tooltip_text ( _ ( " Consume mode " ) )
self . volume = Gtk . VolumeButton ( )
2020-01-28 21:59:14 +03:00
self . volume . set_property ( " size " , self . icon_size )
2020-01-11 13:25:15 +03:00
#connect
self . random_toggled = self . random . connect ( " toggled " , self . set_random )
self . repeat_toggled = self . repeat . connect ( " toggled " , self . set_repeat )
self . single_toggled = self . single . connect ( " toggled " , self . set_single )
self . consume_toggled = self . consume . connect ( " toggled " , self . set_consume )
self . volume_changed = self . volume . connect ( " value-changed " , self . set_volume )
2020-03-30 12:54:04 +03:00
self . options_changed = self . client . emitter . connect ( " options " , self . options_refresh )
self . mixer_changed = self . client . emitter . connect ( " mixer " , self . mixer_refresh )
2020-01-11 13:25:15 +03:00
#packing
ButtonBox = Gtk . ButtonBox ( )
ButtonBox . set_property ( " layout-style " , Gtk . ButtonBoxStyle . EXPAND )
ButtonBox . pack_start ( self . repeat , True , True , 0 )
ButtonBox . pack_start ( self . random , True , True , 0 )
ButtonBox . pack_start ( self . single , True , True , 0 )
ButtonBox . pack_start ( self . consume , True , True , 0 )
self . pack_start ( ButtonBox , True , True , 0 )
self . pack_start ( self . volume , True , True , 0 )
def set_random ( self , widget ) :
if widget . get_active ( ) :
self . client . random ( " 1 " )
else :
self . client . random ( " 0 " )
def set_repeat ( self , widget ) :
if widget . get_active ( ) :
self . client . repeat ( " 1 " )
else :
self . client . repeat ( " 0 " )
def set_single ( self , widget ) :
if widget . get_active ( ) :
self . client . single ( " 1 " )
else :
self . client . single ( " 0 " )
def set_consume ( self , widget ) :
if widget . get_active ( ) :
self . client . consume ( " 1 " )
else :
self . client . consume ( " 0 " )
def set_volume ( self , widget , value ) :
self . client . setvol ( str ( int ( value * 100 ) ) )
2020-03-22 19:05:51 +03:00
def options_refresh ( self , * args ) :
2020-01-11 13:25:15 +03:00
self . repeat . handler_block ( self . repeat_toggled )
self . random . handler_block ( self . random_toggled )
self . single . handler_block ( self . single_toggled )
self . consume . handler_block ( self . consume_toggled )
2020-02-25 00:31:39 +03:00
status = self . client . status ( )
if status [ " repeat " ] == " 0 " :
2020-01-11 13:25:15 +03:00
self . repeat . set_active ( False )
2020-02-25 00:31:39 +03:00
else :
self . repeat . set_active ( True )
if status [ " random " ] == " 0 " :
2020-01-11 13:25:15 +03:00
self . random . set_active ( False )
2020-02-25 00:31:39 +03:00
else :
self . random . set_active ( True )
if status [ " single " ] == " 0 " :
2020-01-11 13:25:15 +03:00
self . single . set_active ( False )
2020-02-25 00:31:39 +03:00
else :
self . single . set_active ( True )
if status [ " consume " ] == " 0 " :
2020-01-11 13:25:15 +03:00
self . consume . set_active ( False )
2020-02-25 00:31:39 +03:00
else :
self . consume . set_active ( True )
2020-01-11 13:25:15 +03:00
self . repeat . handler_unblock ( self . repeat_toggled )
self . random . handler_unblock ( self . random_toggled )
self . single . handler_unblock ( self . single_toggled )
self . consume . handler_unblock ( self . consume_toggled )
2020-02-25 00:31:39 +03:00
2020-03-22 19:05:51 +03:00
def mixer_refresh ( self , * args ) :
2020-02-25 00:31:39 +03:00
self . volume . handler_block ( self . volume_changed )
status = self . client . status ( )
try :
self . volume . set_value ( ( int ( status [ " volume " ] ) / 100 ) )
except :
self . volume . set_value ( 0 )
2020-01-11 13:25:15 +03:00
self . volume . handler_unblock ( self . volume_changed )
2020-05-26 16:04:16 +03:00
class AudioType ( Gtk . Label ) :
2020-01-11 13:25:15 +03:00
def __init__ ( self , client ) :
2020-05-26 16:04:16 +03:00
Gtk . Label . __init__ ( self )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
#connect
2020-05-26 16:04:16 +03:00
self . client . emitter . connect ( " periodic_signal " , self . refresh ) # periodic_signal
self . client . emitter . connect ( " disconnected " , self . clear )
self . client . emitter . connect ( " player " , self . on_player )
2020-05-16 10:55:11 +03:00
2020-05-26 16:04:16 +03:00
def clear ( self , * args ) :
self . set_text ( " " )
2020-05-16 10:55:11 +03:00
2020-05-16 10:42:27 +03:00
def refresh ( self , * args ) :
try :
2020-05-25 22:38:26 +03:00
file_type = self . client . currentsong ( ) [ " file " ] . split ( ' . ' ) [ - 1 ]
2020-01-11 13:25:15 +03:00
status = self . client . status ( )
2020-05-16 10:42:27 +03:00
freq , res , chan = status [ " audio " ] . split ( ' : ' )
freq = str ( float ( freq ) / 1000 )
brate = status [ " bitrate " ]
string = _ ( " %(bitrate)s kb/s, %(frequency)s kHz, %(resolution)s bit, %(channels)s channels, %(file_type)s " ) % { " bitrate " : brate , " frequency " : freq , " resolution " : res , " channels " : chan , " file_type " : file_type }
2020-05-26 16:04:16 +03:00
self . set_text ( string )
2020-05-16 10:42:27 +03:00
except :
2020-05-26 16:04:16 +03:00
self . clear ( )
2020-01-11 13:25:15 +03:00
2020-05-26 16:04:16 +03:00
def on_player ( self , * args ) :
status = self . client . status ( )
if status [ ' state ' ] == " stop " :
self . clear ( )
2020-01-11 13:25:15 +03:00
class ProfileSelect ( Gtk . ComboBoxText ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , client , settings ) :
2020-01-11 13:25:15 +03:00
Gtk . ComboBoxText . __init__ ( self )
#adding vars
self . client = client
self . settings = settings
2020-01-27 00:23:21 +03:00
#connect
2020-01-11 13:25:15 +03:00
self . changed = self . connect ( " changed " , self . on_changed )
2020-03-22 19:05:51 +03:00
self . settings . connect ( " changed::profiles " , self . refresh )
self . settings . connect ( " changed::hosts " , self . refresh )
self . settings . connect ( " changed::ports " , self . refresh )
self . settings . connect ( " changed::passwords " , self . refresh )
self . settings . connect ( " changed::paths " , self . refresh )
2020-01-11 13:25:15 +03:00
2020-03-22 19:05:51 +03:00
self . refresh ( )
2020-01-27 00:23:21 +03:00
2020-03-22 19:05:51 +03:00
def refresh ( self , * args ) :
2020-01-11 13:25:15 +03:00
self . handler_block ( self . changed )
self . remove_all ( )
for profile in self . settings . get_value ( " profiles " ) :
self . append_text ( profile )
2020-02-07 22:13:38 +03:00
self . set_active ( self . settings . get_int ( " active-profile " ) )
2020-01-11 13:25:15 +03:00
self . handler_unblock ( self . changed )
def on_changed ( self , * args ) :
active = self . get_active ( )
self . settings . set_int ( " active-profile " , active )
class ServerStats ( Gtk . Dialog ) :
def __init__ ( self , parent , client ) :
Gtk . Dialog . __init__ ( self , title = _ ( " Stats " ) , transient_for = parent )
2020-05-02 11:56:42 +03:00
self . add_buttons ( Gtk . STOCK_OK , Gtk . ResponseType . OK )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
#Store
#(tag, value)
2020-04-09 01:26:21 +03:00
self . store = Gtk . ListStore ( str , str )
2020-01-11 13:25:15 +03:00
#TreeView
2020-04-09 01:26:21 +03:00
self . treeview = Gtk . TreeView ( model = self . store )
2020-03-04 12:04:47 +03:00
self . treeview . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
self . treeview . set_search_column ( - 1 )
2020-05-02 11:56:42 +03:00
self . treeview . set_headers_visible ( False )
#selection
2020-04-09 01:26:21 +03:00
sel = self . treeview . get_selection ( )
2020-01-11 13:25:15 +03:00
sel . set_mode ( Gtk . SelectionMode . NONE )
#Column
2020-04-09 01:26:21 +03:00
renderer_text = Gtk . CellRendererText ( )
2020-05-02 11:56:42 +03:00
renderer_text_ralign = Gtk . CellRendererText ( xalign = 1.0 )
2020-01-11 13:25:15 +03:00
2020-05-02 11:56:42 +03:00
self . column_tag = Gtk . TreeViewColumn ( " " , renderer_text_ralign , text = 0 )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_tag )
2020-05-02 11:56:42 +03:00
self . column_value = Gtk . TreeViewColumn ( " " , renderer_text , text = 1 )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_value )
2020-05-02 11:56:42 +03:00
self . store . append ( [ " protocol: " , str ( self . client . mpd_version ) ] )
2020-01-11 13:25:15 +03:00
stats = self . client . stats ( )
for key in stats :
2020-05-02 11:56:42 +03:00
print_key = key + " : "
2020-01-11 13:25:15 +03:00
if key == " uptime " or key == " playtime " or key == " db_playtime " :
2020-05-02 11:56:42 +03:00
self . store . append ( [ print_key , str ( datetime . timedelta ( seconds = int ( stats [ key ] ) ) ) ] )
2020-01-11 13:25:15 +03:00
elif key == " db_update " :
2020-05-02 11:56:42 +03:00
self . store . append ( [ print_key , str ( datetime . datetime . fromtimestamp ( int ( stats [ key ] ) ) ) ] )
2020-01-11 13:25:15 +03:00
else :
2020-05-02 11:56:42 +03:00
self . store . append ( [ print_key , stats [ key ] ] )
frame = Gtk . Frame ( )
frame . add ( self . treeview )
self . vbox . pack_start ( frame , True , True , 0 )
2020-05-21 22:18:39 +03:00
self . vbox . set_spacing ( 3 )
2020-01-11 13:25:15 +03:00
self . show_all ( )
2020-05-02 11:56:42 +03:00
self . run ( )
2020-01-11 13:25:15 +03:00
2020-05-18 18:45:47 +03:00
class SearchWindow ( Gtk . Box ) :
2020-03-01 23:21:06 +03:00
def __init__ ( self , client ) :
2020-05-18 18:45:47 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-06-05 18:43:34 +03:00
#tag switcher
self . tags = Gtk . ComboBoxText ( )
2020-01-11 13:25:15 +03:00
#search entry
self . search_entry = Gtk . SearchEntry ( )
#label
self . label = Gtk . Label ( )
self . label . set_xalign ( 1 )
2020-03-24 21:18:29 +03:00
self . label . set_margin_end ( 6 )
2020-01-11 13:25:15 +03:00
2020-06-04 20:34:36 +03:00
#store
#(track, title, artist, album, duration, file)
self . store = Gtk . ListStore ( int , str , str , str , str , str )
2020-03-31 00:48:46 +03:00
#songs view
2020-06-04 20:34:36 +03:00
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 )
2020-01-11 13:25:15 +03:00
2020-05-18 18:45:47 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . songs_view )
2020-05-18 21:47:04 +03:00
#buttons
self . add_button = Gtk . Button ( image = Gtk . Image ( stock = Gtk . STOCK_ADD ) , label = _ ( " Add " ) )
self . add_button . set_sensitive ( False )
self . add_button . set_relief ( Gtk . ReliefStyle . NONE )
self . play_button = Gtk . Button ( image = Gtk . Image ( stock = Gtk . STOCK_MEDIA_PLAY ) , label = _ ( " Play " ) )
self . play_button . set_sensitive ( False )
self . play_button . set_relief ( Gtk . ReliefStyle . NONE )
self . open_button = Gtk . Button ( image = Gtk . Image ( stock = Gtk . STOCK_OPEN ) , label = _ ( " Open " ) )
self . open_button . set_sensitive ( False )
self . open_button . set_relief ( Gtk . ReliefStyle . NONE )
2020-01-11 13:25:15 +03:00
#connect
2020-01-18 00:13:58 +03:00
self . search_entry . connect ( " search-changed " , self . on_search_changed )
2020-06-05 18:43:34 +03:00
self . tags . connect ( " changed " , self . on_search_changed )
2020-05-18 21:47:04 +03:00
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 )
2020-06-05 18:43:34 +03:00
self . client . emitter . connect ( " reconnected " , self . on_reconnected )
2020-01-11 13:25:15 +03:00
#packing
2020-06-05 18:43:34 +03:00
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 )
2020-05-18 18:45:47 +03:00
frame = FocusFrame ( )
frame . set_widget ( self . songs_view )
frame . add ( scroll )
2020-05-18 21:47:04 +03:00
ButtonBox = Gtk . ButtonBox ( spacing = 1 )
ButtonBox . set_property ( " border-width " , 1 )
ButtonBox . pack_start ( self . add_button , True , True , 0 )
ButtonBox . pack_start ( self . play_button , True , True , 0 )
ButtonBox . pack_start ( self . open_button , True , True , 0 )
hbox = Gtk . Box ( orientation = Gtk . Orientation . HORIZONTAL )
hbox . pack_start ( ButtonBox , 0 , False , False )
hbox . pack_end ( self . label , 0 , False , False )
2020-06-05 18:43:34 +03:00
self . pack_start ( vbox , False , False , 0 )
2020-05-18 18:45:47 +03:00
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 )
2020-05-18 21:47:04 +03:00
self . pack_start ( hbox , False , False , 0 )
2020-03-01 23:21:06 +03:00
2020-05-17 21:46:19 +03:00
def start ( self ) :
self . search_entry . grab_focus ( )
2020-01-11 13:25:15 +03:00
2020-05-17 23:52:10 +03:00
def started ( self ) :
return self . search_entry . has_focus ( )
2020-05-21 22:18:39 +03:00
def clear ( self , * args ) :
self . songs_view . clear ( )
self . search_entry . set_text ( " " )
2020-06-05 18:43:34 +03:00
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 )
2020-05-21 22:18:39 +03:00
2020-01-11 13:25:15 +03:00
def on_search_changed ( self , widget ) :
2020-03-31 00:48:46 +03:00
self . songs_view . clear ( )
2020-05-17 21:46:19 +03:00
self . label . set_text ( " " )
if len ( self . search_entry . get_text ( ) ) > 1 :
2020-06-05 18:43:34 +03:00
songs = self . client . search ( self . tags . get_active_text ( ) , self . search_entry . get_text ( ) )
2020-06-04 20:34:36 +03:00
for s in songs :
2020-06-04 21:36:34 +03:00
song = ClientHelper . extend_song_for_display ( ClientHelper . song_to_str_dict ( s ) )
2020-06-04 20:34:36 +03:00
self . store . append ( [ int ( song [ " track " ] ) , song [ " title " ] , song [ " artist " ] , song [ " album " ] , song [ " human_duration " ] , song [ " file " ] ] )
2020-05-17 21:46:19 +03:00
self . label . set_text ( _ ( " hits: %i " ) % ( self . songs_view . count ( ) ) )
2020-05-18 21:47:04 +03:00
if self . songs_view . count ( ) == 0 :
self . add_button . set_sensitive ( False )
self . play_button . set_sensitive ( False )
self . open_button . set_sensitive ( False )
else :
self . add_button . set_sensitive ( True )
self . play_button . set_sensitive ( True )
self . open_button . set_sensitive ( True )
def on_add_clicked ( self , * args ) :
self . client . files_to_playlist ( self . songs_view . get_files ( ) , True )
def on_play_clicked ( self , * args ) :
self . client . files_to_playlist ( self . songs_view . get_files ( ) , False , True )
def on_open_clicked ( self , * args ) :
2020-05-18 22:04:44 +03:00
self . client . files_to_playlist ( self . songs_view . get_files ( ) , False )
2020-01-11 13:25:15 +03:00
2020-05-21 22:18:39 +03:00
class LyricsWindow ( Gtk . Overlay ) :
def __init__ ( self , client , settings ) :
Gtk . Overlay . __init__ ( self )
2020-01-11 13:25:15 +03:00
#adding vars
self . settings = settings
2020-02-25 00:31:39 +03:00
self . client = client
2020-01-11 13:25:15 +03:00
#widgets
2020-05-24 20:37:08 +03:00
self . text_view = Gtk . TextView ( )
self . text_view . set_editable ( False )
self . text_view . set_left_margin ( 5 )
self . text_view . set_bottom_margin ( 5 )
self . text_view . set_cursor_visible ( False )
self . text_view . set_wrap_mode ( Gtk . WrapMode . WORD )
self . text_view . set_justification ( Gtk . Justification . CENTER )
self . text_buffer = self . text_view . get_buffer ( )
2020-01-11 13:25:15 +03:00
2020-05-21 22:18:39 +03:00
#scroll
self . scroll = Gtk . ScrolledWindow ( )
self . scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
2020-05-24 20:37:08 +03:00
self . scroll . add ( self . text_view )
2020-05-21 22:18:39 +03:00
#frame
2020-06-06 15:20:05 +03:00
frame = FocusFrame ( )
frame . set_widget ( self . text_view )
2020-05-21 22:18:39 +03:00
style_context = frame . get_style_context ( )
provider = Gtk . CssProvider ( )
css = b """ * { border: 0px; background-color: @theme_base_color; opacity: 0.9;} """
provider . load_from_data ( css )
style_context . add_provider ( provider , 800 )
#close button
2020-05-23 15:09:58 +03:00
close_button = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " window-close-symbolic " , Gtk . IconSize . BUTTON ) )
2020-05-23 17:14:29 +03:00
close_button . set_margin_top ( 6 )
close_button . set_margin_end ( 6 )
2020-05-21 22:18:39 +03:00
style_context = close_button . get_style_context ( )
2020-05-23 17:14:29 +03:00
style_context . add_class ( " circular " )
2020-05-21 22:18:39 +03:00
close_button . set_halign ( 2 )
close_button . set_valign ( 1 )
2020-01-11 13:25:15 +03:00
#connect
2020-03-30 12:54:04 +03:00
self . file_changed = self . client . emitter . connect ( " playing_file_changed " , self . refresh )
2020-02-29 00:11:46 +03:00
self . connect ( " destroy " , self . remove_handlers )
2020-05-21 22:18:39 +03:00
close_button . connect ( " clicked " , self . on_close_button_clicked )
2020-01-11 13:25:15 +03:00
#packing
2020-05-21 22:18:39 +03:00
frame . add ( self . scroll )
self . add ( frame )
self . add_overlay ( close_button )
2020-01-11 13:25:15 +03:00
self . show_all ( )
2020-03-22 20:16:21 +03:00
self . refresh ( )
2020-01-11 13:25:15 +03:00
2020-02-29 00:11:46 +03:00
def remove_handlers ( self , * args ) :
2020-03-30 12:54:04 +03:00
self . client . emitter . disconnect ( self . file_changed )
2020-02-29 00:11:46 +03:00
2020-03-22 17:03:07 +03:00
def display_lyrics ( self , current_song ) :
2020-05-24 20:37:08 +03:00
GLib . idle_add ( self . text_buffer . set_text , _ ( " searching... " ) , - 1 )
2020-02-25 00:31:39 +03:00
try :
2020-03-22 17:03:07 +03:00
text = self . getLyrics ( current_song [ " artist " ] , current_song [ " title " ] )
2020-02-25 00:31:39 +03:00
except :
2020-05-18 18:24:09 +03:00
text = _ ( " lyrics not found " )
2020-05-24 20:37:08 +03:00
GLib . idle_add ( self . text_buffer . set_text , text , - 1 )
2020-02-25 00:31:39 +03:00
2020-03-22 19:05:51 +03:00
def refresh ( self , * args ) :
2020-06-04 21:36:34 +03:00
update_thread = threading . Thread ( target = self . display_lyrics , kwargs = { " current_song " : ClientHelper . song_to_first_str_dict ( self . client . currentsong ( ) ) } , daemon = True )
2020-03-22 16:25:04 +03:00
update_thread . start ( )
2020-01-11 13:25:15 +03:00
def getLyrics ( self , singer , song ) : #partially copied from PyLyrics 1.1.0
#Replace spaces with _
2020-04-09 01:26:21 +03:00
singer = singer . replace ( ' ' , ' _ ' )
song = song . replace ( ' ' , ' _ ' )
r = requests . get ( ' http://lyrics.wikia.com/ {0} : {1} ' . format ( singer , song ) )
s = BeautifulSoup ( r . text )
2020-01-11 13:25:15 +03:00
#Get main lyrics holder
2020-04-09 01:26:21 +03:00
lyrics = s . find ( " div " , { ' class ' : ' lyricbox ' } )
2020-01-11 13:25:15 +03:00
if lyrics is None :
raise ValueError ( " Song or Singer does not exist or the API does not have Lyrics " )
return None
#Remove Scripts
[ s . extract ( ) for s in lyrics ( ' script ' ) ]
#Remove Comments
2020-04-09 01:26:21 +03:00
comments = lyrics . findAll ( text = lambda text : isinstance ( text , Comment ) )
2020-01-11 13:25:15 +03:00
[ comment . extract ( ) for comment in comments ]
#Remove span tag (Needed for instrumantal)
if not lyrics . span == None :
lyrics . span . extract ( )
#Remove unecessary tags
for tag in [ ' div ' , ' i ' , ' b ' , ' a ' ] :
for match in lyrics . findAll ( tag ) :
match . replaceWithChildren ( )
#Get output as a string and remove non unicode characters and replace <br> with newlines
2020-04-09 01:26:21 +03:00
output = str ( lyrics ) . encode ( ' utf-8 ' , errors = ' replace ' ) [ 22 : - 6 : ] . decode ( " utf-8 " ) . replace ( ' \n ' , ' ' ) . replace ( ' <br/> ' , ' \n ' )
2020-01-11 13:25:15 +03:00
try :
return output
except :
return output . encode ( ' utf-8 ' )
2020-05-21 22:18:39 +03:00
def on_close_button_clicked ( self , * args ) :
self . destroy ( )
2020-01-11 13:25:15 +03:00
class MainWindow ( Gtk . ApplicationWindow ) :
2020-03-30 12:54:04 +03:00
def __init__ ( self , app , client , settings ) :
2020-01-11 13:25:15 +03:00
Gtk . ApplicationWindow . __init__ ( self , title = ( " mpdevil " ) , application = app )
2020-03-03 17:59:18 +03:00
Notify . init ( " mpdevil " )
2020-01-11 13:25:15 +03:00
self . set_icon_name ( " mpdevil " )
2020-04-09 01:26:21 +03:00
self . settings = settings
2020-01-11 13:25:15 +03:00
self . set_default_size ( self . settings . get_int ( " width " ) , self . settings . get_int ( " height " ) )
#adding vars
2020-02-25 00:31:39 +03:00
self . app = app
2020-01-11 13:25:15 +03:00
self . client = client
2020-01-28 21:59:14 +03:00
self . icon_size = self . settings . get_gtk_icon_size ( " icon-size " )
2020-05-21 22:18:39 +03:00
self . use_csd = self . settings . get_boolean ( " use-csd " )
2020-01-11 13:25:15 +03:00
2020-03-21 00:09:13 +03:00
#MPRIS
DBusGMainLoop ( set_as_default = True )
2020-04-09 01:26:21 +03:00
self . dbus_service = MPRISInterface ( self , self . client , self . settings )
2020-03-21 00:09:13 +03:00
2020-01-11 13:25:15 +03:00
#actions
2020-04-09 01:26:21 +03:00
save_action = Gio . SimpleAction . new ( " save " , None )
2020-01-11 13:25:15 +03:00
save_action . connect ( " activate " , self . on_save )
self . add_action ( save_action )
2020-04-09 01:26:21 +03:00
settings_action = Gio . SimpleAction . new ( " settings " , None )
2020-01-11 13:25:15 +03:00
settings_action . connect ( " activate " , self . on_settings )
self . add_action ( settings_action )
2020-04-09 01:26:21 +03:00
stats_action = Gio . SimpleAction . new ( " stats " , None )
2020-01-11 13:25:15 +03:00
stats_action . connect ( " activate " , self . on_stats )
self . add_action ( stats_action )
2020-05-16 11:58:13 +03:00
self . update_action = Gio . SimpleAction . new ( " update " , None )
self . update_action . connect ( " activate " , self . on_update )
self . add_action ( self . update_action )
2020-01-11 13:25:15 +03:00
2020-05-26 23:53:59 +03:00
self . help_action = Gio . SimpleAction . new ( " help " , None )
self . help_action . connect ( " activate " , self . on_help )
self . add_action ( self . help_action )
2020-01-11 13:25:15 +03:00
#widgets
2020-03-30 12:54:04 +03:00
self . browser = Browser ( self . client , self . settings , self )
self . profiles = ProfileSelect ( self . client , self . settings )
2020-01-11 13:25:15 +03:00
self . profiles . set_tooltip_text ( _ ( " Select profile " ) )
2020-03-30 12:54:04 +03:00
self . control = ClientControl ( self . client , self . settings )
self . progress = SeekBar ( self . client )
self . play_opts = PlaybackOptions ( self . client , self . settings )
2020-01-11 13:25:15 +03:00
#menu
2020-05-28 23:46:38 +03:00
subsection = Gio . Menu ( )
subsection . append ( _ ( " Settings " ) , " win.settings " )
subsection . append ( _ ( " Help " ) , " win.help " )
subsection . append ( _ ( " About " ) , " app.about " )
subsection . append ( _ ( " Quit " ) , " app.quit " )
2020-04-09 01:26:21 +03:00
menu = Gio . Menu ( )
2020-03-04 18:39:59 +03:00
menu . append ( _ ( " Save window layout " ) , " win.save " )
2020-01-11 13:25:15 +03:00
menu . append ( _ ( " Update database " ) , " win.update " )
menu . append ( _ ( " Server stats " ) , " win.stats " )
2020-05-28 23:46:38 +03:00
menu . append_section ( None , subsection )
2020-01-11 13:25:15 +03:00
2020-04-09 01:26:21 +03:00
menu_button = Gtk . MenuButton . new ( )
menu_popover = Gtk . Popover . new_from_model ( menu_button , menu )
2020-01-11 13:25:15 +03:00
menu_button . set_popover ( menu_popover )
2020-03-22 23:49:55 +03:00
menu_button . set_tooltip_text ( _ ( " Menu " ) )
2020-01-11 13:25:15 +03:00
#connect
2020-02-07 22:13:38 +03:00
self . settings . connect ( " changed::profiles " , self . on_settings_changed )
2020-03-30 12:54:04 +03:00
self . client . emitter . connect ( " playing_file_changed " , self . on_file_changed )
self . client . emitter . connect ( " disconnected " , self . on_disconnected )
self . client . emitter . connect ( " reconnected " , self . on_reconnected )
2020-01-11 13:25:15 +03:00
#unmap space
binding_set = Gtk . binding_set_find ( ' GtkTreeView ' )
Gtk . binding_entry_remove ( binding_set , 32 , Gdk . ModifierType . MOD2_MASK )
#map space play/pause
self . connect ( " key-press-event " , self . on_key_press_event )
#packing
2020-03-24 18:14:01 +03:00
self . vbox = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL )
self . action_bar = Gtk . ActionBar ( )
2020-01-11 13:25:15 +03:00
self . vbox . pack_start ( self . browser , True , True , 0 )
2020-03-24 18:14:01 +03:00
self . vbox . pack_start ( self . action_bar , False , False , 0 )
self . action_bar . pack_start ( self . control )
self . action_bar . pack_start ( self . progress )
2020-05-27 18:36:49 +03:00
self . action_bar . pack_start ( self . play_opts )
2020-05-21 22:18:39 +03:00
if self . use_csd :
menu_button . set_image ( image = Gtk . Image . new_from_icon_name ( " open-menu-symbolic " , Gtk . IconSize . BUTTON ) )
self . header_bar = Gtk . HeaderBar ( )
self . header_bar . set_show_close_button ( True )
self . header_bar . set_title ( " mpdevil " )
self . set_titlebar ( self . header_bar )
self . header_bar . pack_start ( self . browser . back_to_album_button )
self . header_bar . pack_start ( self . browser . genre_select )
self . header_bar . pack_end ( menu_button )
self . header_bar . pack_end ( self . profiles )
self . header_bar . pack_end ( self . browser . search_button )
else :
menu_button . set_image ( image = Gtk . Image . new_from_icon_name ( " open-menu-symbolic " , self . icon_size ) )
2020-05-27 18:36:49 +03:00
self . action_bar . pack_start ( Gtk . Separator . new ( orientation = Gtk . Orientation . VERTICAL ) )
2020-05-21 22:18:39 +03:00
self . action_bar . pack_start ( self . profiles )
2020-05-27 18:36:49 +03:00
self . action_bar . pack_start ( menu_button )
2020-01-11 13:25:15 +03:00
self . add ( self . vbox )
2020-01-27 00:38:06 +03:00
2020-01-19 02:00:40 +03:00
self . show_all ( )
2020-05-21 22:33:39 +03:00
if self . settings . get_boolean ( " maximize " ) :
self . maximize ( )
2020-03-24 18:41:37 +03:00
self . on_settings_changed ( ) #hide profiles button
2020-05-16 10:42:27 +03:00
self . client . start ( ) #connect client
2020-01-11 13:25:15 +03:00
2020-03-22 19:05:51 +03:00
def on_file_changed ( self , * args ) :
2020-02-25 00:31:39 +03:00
try :
2020-05-13 19:02:39 +03:00
song = self . client . currentsong ( )
if song == { } :
raise ValueError ( " Song out of range " )
2020-06-04 21:36:34 +03:00
song = ClientHelper . extend_song_for_display ( ClientHelper . song_to_str_dict ( song ) )
2020-05-12 18:39:57 +03:00
if song [ " date " ] != " " :
date = " ( " + song [ " date " ] + " ) "
else :
date = " "
2020-05-21 22:18:39 +03:00
if self . use_csd :
self . header_bar . set_title ( song [ " title " ] + " - " + song [ " artist " ] )
self . header_bar . set_subtitle ( song [ " album " ] + date )
else :
2020-05-24 23:42:43 +03:00
self . set_title ( song [ " title " ] + " - " + song [ " artist " ] + " - " + song [ " album " ] + date )
2020-03-22 16:25:04 +03:00
if self . settings . get_boolean ( " send-notify " ) :
if not self . is_active ( ) and self . client . status ( ) [ " state " ] == " play " :
2020-05-12 18:39:57 +03:00
notify = Notify . Notification . new ( song [ " title " ] , song [ " artist " ] + " \n " + song [ " album " ] + date )
2020-03-22 16:25:04 +03:00
pixbuf = Cover ( lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = song [ " file " ] ) . get_pixbuf ( 400 )
notify . set_image_from_pixbuf ( pixbuf )
notify . show ( )
2020-02-25 00:31:39 +03:00
except :
2020-05-21 22:18:39 +03:00
if self . use_csd :
self . header_bar . set_title ( " mpdevil " )
self . header_bar . set_subtitle ( " " )
else :
self . set_title ( " mpdevil " )
2020-02-25 00:31:39 +03:00
def on_reconnected ( self , * args ) :
2020-03-21 00:09:13 +03:00
self . dbus_service . acquire_name ( )
2020-02-25 00:31:39 +03:00
self . progress . set_sensitive ( True )
self . control . set_sensitive ( True )
self . play_opts . set_sensitive ( True )
2020-03-29 22:34:51 +03:00
self . browser . back_to_album ( )
2020-02-25 00:31:39 +03:00
def on_disconnected ( self , * args ) :
2020-03-21 00:09:13 +03:00
self . dbus_service . release_name ( )
2020-05-21 22:18:39 +03:00
if self . use_csd :
self . header_bar . set_title ( " mpdevil " )
self . header_bar . set_subtitle ( " (not connected) " )
else :
self . set_title ( " mpdevil (not connected) " )
2020-02-25 00:31:39 +03:00
self . songid_playing = None
self . progress . set_sensitive ( False )
self . control . set_sensitive ( False )
self . play_opts . set_sensitive ( False )
2020-01-11 13:25:15 +03:00
def on_key_press_event ( self , widget , event ) :
2020-02-05 23:35:19 +03:00
if event . keyval == 32 : #space
2020-05-17 23:52:10 +03:00
if not self . browser . search_started ( ) :
self . control . play_button . grab_focus ( )
2020-05-26 23:53:59 +03:00
elif event . keyval == 269025044 : #AudioPlay
2020-03-27 20:16:50 +03:00
self . control . play_button . grab_focus ( )
2020-02-05 22:29:34 +03:00
self . control . play_button . emit ( " clicked " )
2020-05-19 17:50:20 +03:00
elif event . keyval == 269025047 : #AudioNext
2020-03-27 20:16:50 +03:00
self . control . next_button . grab_focus ( )
2020-02-05 22:29:34 +03:00
self . control . next_button . emit ( " clicked " )
2020-05-19 17:50:20 +03:00
elif event . keyval == 43 or event . keyval == 65451 : #+
if not self . browser . search_started ( ) :
self . control . next_button . grab_focus ( )
self . control . next_button . emit ( " clicked " )
elif event . keyval == 269025046 : #AudioPrev
2020-03-27 20:16:50 +03:00
self . control . prev_button . grab_focus ( )
2020-02-05 22:29:34 +03:00
self . control . prev_button . emit ( " clicked " )
2020-05-19 17:50:20 +03:00
elif event . keyval == 45 or event . keyval == 65453 : #-
if not self . browser . search_started ( ) :
self . control . prev_button . grab_focus ( )
self . control . prev_button . emit ( " clicked " )
2020-02-29 10:32:07 +03:00
elif event . keyval == 65307 : #esc
2020-03-29 22:34:51 +03:00
self . browser . back_to_album ( )
2020-03-03 18:11:30 +03:00
elif event . keyval == 65450 : #*
2020-05-19 17:50:20 +03:00
if not self . browser . search_started ( ) :
self . progress . scale . grab_focus ( )
self . progress . seek_forward ( )
2020-03-03 18:11:30 +03:00
elif event . keyval == 65455 : #/
2020-05-19 17:50:20 +03:00
if not self . browser . search_started ( ) :
self . progress . scale . grab_focus ( )
self . progress . seek_backward ( )
2020-05-16 11:58:13 +03:00
elif event . keyval == 65474 : #F5
self . update_action . emit ( " activate " , None )
2020-05-26 23:53:59 +03:00
elif event . keyval == 65470 : #F1
self . help_action . emit ( " activate " , None )
2020-01-11 13:25:15 +03:00
def on_save ( self , action , param ) :
size = self . get_size ( )
self . settings . set_int ( " width " , size [ 0 ] )
self . settings . set_int ( " height " , size [ 1 ] )
2020-05-21 22:33:39 +03:00
self . settings . set_boolean ( " maximize " , self . is_maximized ( ) )
2020-01-11 13:25:15 +03:00
self . browser . save_settings ( )
def on_settings ( self , action , param ) :
2020-04-09 01:26:21 +03:00
settings = SettingsDialog ( self , self . settings )
2020-01-11 13:25:15 +03:00
settings . run ( )
settings . destroy ( )
def on_stats ( self , action , param ) :
if self . client . connected ( ) :
2020-04-09 01:26:21 +03:00
stats = ServerStats ( self , self . client )
2020-01-11 13:25:15 +03:00
stats . destroy ( )
def on_update ( self , action , param ) :
if self . client . connected ( ) :
self . client . update ( )
2020-05-26 23:53:59 +03:00
def on_help ( self , action , param ) :
Gtk . show_uri_on_window ( self , " https://github.com/SoongNoonien/mpdevil/wiki/Usage " , Gdk . CURRENT_TIME )
2020-02-07 22:13:38 +03:00
def on_settings_changed ( self , * args ) :
if len ( self . settings . get_value ( " profiles " ) ) > 1 :
2020-03-24 18:14:01 +03:00
self . profiles . set_property ( " visible " , True )
else :
self . profiles . set_property ( " visible " , False )
2020-02-07 22:13:38 +03:00
2020-01-11 13:25:15 +03:00
class mpdevil ( Gtk . Application ) :
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , application_id = " org.mpdevil " , flags = Gio . ApplicationFlags . FLAGS_NONE , * * kwargs )
2020-04-09 01:26:21 +03:00
self . settings = Settings ( )
2020-01-27 00:23:21 +03:00
self . client = Client ( self . settings )
2020-01-11 13:25:15 +03:00
self . window = None
def do_activate ( self ) :
2020-01-19 02:00:40 +03:00
if not self . window : #allow just one instance
2020-04-09 01:26:21 +03:00
self . window = MainWindow ( self , self . client , self . settings )
2020-01-19 02:00:40 +03:00
self . window . connect ( " delete-event " , self . on_delete_event )
self . window . present ( )
2020-01-11 13:25:15 +03:00
def do_startup ( self ) :
Gtk . Application . do_startup ( self )
2020-04-09 01:26:21 +03:00
action = Gio . SimpleAction . new ( " about " , None )
2020-01-11 13:25:15 +03:00
action . connect ( " activate " , self . on_about )
self . add_action ( action )
2020-04-09 01:26:21 +03:00
action = Gio . SimpleAction . new ( " quit " , None )
2020-01-11 13:25:15 +03:00
action . connect ( " activate " , self . on_quit )
self . add_action ( action )
def on_delete_event ( self , * args ) :
if self . settings . get_boolean ( " stop-on-quit " ) and self . client . connected ( ) :
self . client . stop ( )
self . quit ( )
def on_about ( self , action , param ) :
dialog = Gtk . AboutDialog ( transient_for = self . window , modal = True )
dialog . set_program_name ( NAME )
dialog . set_version ( VERSION )
dialog . set_comments ( _ ( " A small MPD client written in python " ) )
dialog . set_authors ( [ " Martin Wagner " ] )
2020-01-12 18:27:23 +03:00
dialog . set_website ( " https://github.com/SoongNoonien/mpdevil " )
2020-02-01 17:44:51 +03:00
dialog . set_copyright ( " \xa9 2020 Martin Wagner " )
2020-01-11 13:25:15 +03:00
dialog . set_logo_icon_name ( PACKAGE )
dialog . run ( )
dialog . destroy ( )
def on_quit ( self , action , param ) :
if self . settings . get_boolean ( " stop-on-quit " ) and self . client . connected ( ) :
self . client . stop ( )
self . quit ( )
if __name__ == ' __main__ ' :
2020-04-09 01:26:21 +03:00
app = mpdevil ( )
2020-01-11 13:25:15 +03:00
app . run ( sys . argv )