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
import gi #python-gobject dev-python/pygobject:3[${PYTHON_USEDEP}]
gi . require_version ( ' Gtk ' , ' 3.0 ' )
2020-01-27 22:27:35 +03:00
from gi . repository import Gtk , Gio , Gdk , GdkPixbuf , Pango , GObject , GLib
2020-01-11 13:25:15 +03:00
from mpd import MPDClient
import requests #dev-python/requests
from bs4 import BeautifulSoup , Comment #, NavigableString #dev-python/beautifulsoup
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
DATADIR = ' @datadir@ '
NAME = ' mpdevil '
VERSION = ' @version@ '
PACKAGE = NAME . lower ( )
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 ' )
class IntEntry ( Gtk . SpinButton ) :
def __init__ ( self , default , lower , upper ) :
Gtk . SpinButton . __init__ ( self )
adj = Gtk . Adjustment ( value = default , lower = lower , upper = upper , step_increment = 1 )
self . set_adjustment ( adj )
def get_int ( self ) :
return int ( self . get_value ( ) )
def set_int ( self , value ) :
self . set_value ( value )
class Cover ( object ) :
def __init__ ( self , client , lib_path , song_file ) :
self . client = client
self . lib_path = lib_path
self . path = None
if not song_file == None :
head_tail = os . path . split ( song_file )
path = ( self . lib_path + " / " + head_tail [ 0 ] + " / " )
if os . path . exists ( path ) :
filelist = [ file for file in os . listdir ( path ) if file . endswith ( ' .jpg ' ) or file . endswith ( ' .png ' ) or file . endswith ( ' .gif ' ) ]
if not filelist == [ ] :
self . path = ( path + filelist [ 0 ] )
def get_pixbuf ( self , size ) :
if self . path == None :
self . path = Gtk . IconTheme . get_default ( ) . lookup_icon ( " mpdevil " , size , Gtk . IconLookupFlags . FORCE_SVG ) . get_filename ( ) #fallback cover
return GdkPixbuf . Pixbuf . new_from_file_at_size ( self . path , size , size )
class Client ( MPDClient ) :
2020-01-27 00:23:21 +03:00
def __init__ ( self , settings ) :
2020-01-11 13:25:15 +03:00
MPDClient . __init__ ( self )
2020-01-27 00:23:21 +03:00
self . settings = settings
2020-02-01 14:45:34 +03:00
self . song_to_delete = " "
2020-02-22 17:22:57 +03:00
self . settings . connect ( " changed::active-profile " , self . on_settings_changed )
2020-01-27 00:23:21 +03:00
def try_connect_default ( self ) :
active = self . settings . get_int ( " active-profile " )
try :
self . connect ( self . settings . get_value ( " hosts " ) [ active ] , self . settings . get_value ( " ports " ) [ active ] )
2020-02-01 15:27:46 +03:00
if self . settings . get_value ( " passwords " ) [ active ] == " " :
self . password ( None )
else :
self . password ( self . settings . get_value ( " passwords " ) [ active ] )
2020-01-27 00:23:21 +03:00
except :
pass
2020-01-11 13:25:15 +03:00
def connected ( self ) :
try :
self . ping ( )
return True
except :
return False
2020-02-01 14:45:34 +03:00
def album_to_playlist ( self , album , artist , year , append , force = False ) :
if append :
2020-02-26 01:37:27 +03:00
songs = self . find ( " album " , album , " date " , year , self . settings . get_artist_type ( ) , artist )
2020-02-01 14:45:34 +03:00
if not songs == [ ] :
for song in songs :
self . add ( song [ " file " ] )
else :
if self . settings . get_boolean ( " add-album " ) and not force and not self . status ( ) [ " state " ] == " stop " :
status = self . status ( )
self . moveid ( status [ " songid " ] , 0 )
self . song_to_delete = self . playlistinfo ( ) [ 0 ] [ " file " ]
try :
self . delete ( ( 1 , ) ) # delete all songs, but the first. #bad song index possible
except :
pass
2020-02-26 01:37:27 +03:00
songs = self . find ( " album " , album , " date " , year , self . settings . get_artist_type ( ) , artist )
2020-02-01 14:45:34 +03:00
if not songs == [ ] :
for song in songs :
if not song [ " file " ] == self . song_to_delete :
self . add ( song [ " file " ] )
else :
self . move ( 0 , ( len ( self . playlist ( ) ) - 1 ) )
self . song_to_delete = " "
else :
2020-02-26 01:37:27 +03:00
songs = self . find ( " album " , album , " date " , year , self . settings . get_artist_type ( ) , artist )
2020-02-01 14:45:34 +03:00
if not songs == [ ] :
self . stop ( )
self . clear ( )
for song in songs :
self . add ( song [ " file " ] )
self . play ( )
2020-02-22 17:22:57 +03:00
def on_settings_changed ( self , * args ) :
self . disconnect ( )
2020-02-25 00:31:39 +03:00
class MpdEventEmitter ( GObject . Object ) :
def __init__ ( self , settings ) :
super ( ) . __init__ ( )
self . client = Client ( settings )
GLib . timeout_add ( 100 , self . watch )
self . connected = True
def watch ( self , * args ) :
try :
a = self . client . noidle ( )
for i in a :
self . emit ( i )
except :
pass
try :
self . client . send_idle ( )
except :
self . client . try_connect_default ( )
if self . client . connected ( ) :
self . emit ( " reconnected " )
self . connected = True
elif self . connected :
self . emit ( " disconnected " )
self . connected = False
return True
@GObject.Signal
def database ( self ) :
pass
@GObject.Signal
def update ( self ) :
pass
@GObject.Signal
def stored_playlist ( self ) :
pass
@GObject.Signal
def playlist ( self ) :
pass
@GObject.Signal
def player ( self ) :
pass
@GObject.Signal
def mixer ( self ) :
pass
@GObject.Signal
def output ( self ) :
pass
@GObject.Signal
def options ( self ) :
pass
@GObject.Signal
def sticker ( self ) :
pass
@GObject.Signal
def subscription ( self ) :
pass
@GObject.Signal
def message ( self ) :
pass
@GObject.Signal
def disconnected ( self ) :
pass
@GObject.Signal
def reconnected ( self ) :
pass
2020-02-22 17:22:57 +03:00
2020-01-28 20:39:18 +03:00
class Settings ( Gio . Settings ) :
BASE_KEY = " org.mpdevil "
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 ) :
if self . get_boolean ( " show-all-artists " ) :
return ( " artist " )
else :
return ( " albumartist " )
2020-01-18 00:13:58 +03:00
class AlbumDialog ( Gtk . Dialog ) :
2020-02-26 01:37:27 +03:00
def __init__ ( self , parent , client , settings , album , artist , year ) :
2020-01-18 00:13:58 +03:00
Gtk . Dialog . __init__ ( self , title = ( artist + " - " + album + " ( " + year + " ) " ) , transient_for = parent )
2020-01-18 18:23:52 +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-01-18 00:13:58 +03:00
self . set_default_size ( 800 , 600 )
#adding vars
self . client = client
2020-02-26 01:37:27 +03:00
self . settings = settings
2020-01-18 00:13:58 +03:00
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
#Store
#(track, title, artist, duration, file)
self . store = Gtk . ListStore ( str , str , str , str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_search_column ( - 1 )
self . treeview . columns_autosize ( )
self . selection = self . treeview . get_selection ( )
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
#Column
renderer_text = Gtk . CellRendererText ( )
self . column_track = Gtk . TreeViewColumn ( _ ( " No " ) , renderer_text , text = 0 )
self . column_track . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
self . column_track . set_property ( " resizable " , False )
self . treeview . 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 . treeview . 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 . treeview . append_column ( self . column_artist )
self . column_time = Gtk . TreeViewColumn ( _ ( " Length " ) , renderer_text , text = 3 )
self . column_time . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
self . column_time . set_property ( " resizable " , False )
self . treeview . append_column ( self . column_time )
self . populate_treeview ( album , artist , year )
#connect
self . title_activated = self . treeview . connect ( " row-activated " , self . on_row_activated )
#packing
scroll . add ( self . treeview )
self . vbox . pack_start ( scroll , True , True , 0 ) #vbox default widget of dialogs
2020-01-19 02:00:40 +03:00
self . vbox . set_spacing ( 6 )
2020-01-18 00:13:58 +03:00
self . show_all ( )
#selection workaround
self . selection . unselect_all ( )
self . title_change = self . selection . connect ( " changed " , self . on_selection_change )
def on_row_activated ( self , widget , path , view_column ) :
treeiter = self . store . get_iter ( path )
selected_title = self . store . get_value ( treeiter , 4 )
self . client . clear ( )
self . client . add ( selected_title )
2020-01-26 23:32:12 +03:00
self . client . play ( )
2020-01-18 00:13:58 +03:00
def on_selection_change ( self , widget ) :
treeiter = widget . get_selected ( ) [ 1 ]
if not treeiter == None :
selected_title = self . store . get_value ( treeiter , 4 )
self . client . add ( selected_title )
def populate_treeview ( self , album , artist , year ) :
2020-02-26 01:37:27 +03:00
songs = self . client . find ( " album " , album , " date " , year , self . settings . get_artist_type ( ) , artist )
2020-01-18 00:13:58 +03:00
if not songs == [ ] :
for song in songs :
try :
title = song [ " title " ]
except :
title = _ ( " Unknown Title " )
try :
artist = song [ " artist " ]
except :
artist = _ ( " Unknown Artist " )
try :
track = song [ " track " ] . zfill ( 2 )
except :
track = " 00 "
try :
dura = float ( song [ " duration " ] )
except :
dura = 0.0
duration = str ( datetime . timedelta ( seconds = int ( dura ) ) )
self . store . append ( [ track , title , artist , duration , song [ " file " ] ] )
2020-02-27 22:05:48 +03:00
class GenreSelect ( Gtk . Box ) :
2020-02-26 01:37:27 +03:00
def __init__ ( self , client , settings , emitter ) :
2020-02-27 22:05:48 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . HORIZONTAL , spacing = 4 )
#adding vars
self . client = client
self . settings = settings
self . emitter = emitter
self . combo = Gtk . ComboBoxText ( )
#connect
self . combo_changed = self . combo . connect ( " changed " , self . on_combo_changed )
self . update_signal = self . emitter . connect ( " update " , self . refresh )
self . pack_start ( self . combo , True , True , 0 )
@GObject.Signal
def changed ( self ) :
self . emitter . handler_block ( self . update_signal )
self . emitter . emit ( " update " )
self . emitter . handler_unblock ( self . update_signal )
2020-02-29 01:23:53 +03:00
def deactivate ( self ) :
self . combo . set_active ( 0 )
2020-02-27 22:05:48 +03:00
def refresh ( self , * args ) :
self . combo . handler_block ( self . combo_changed )
self . combo . remove_all ( )
2020-02-28 13:47:57 +03:00
self . combo . append_text ( _ ( " all genres " ) )
2020-02-27 22:05:48 +03:00
for genre in self . client . list ( " genre " ) :
self . combo . append_text ( genre )
self . combo . set_active ( 0 )
self . combo . handler_unblock ( self . combo_changed )
def get_value ( self ) :
2020-02-28 13:47:57 +03:00
if self . combo . get_active ( ) == 0 :
2020-02-27 22:05:48 +03:00
return None
2020-02-28 13:47:57 +03:00
else :
return self . combo . get_active_text ( )
2020-02-27 22:05:48 +03:00
def on_combo_changed ( self , * args ) :
self . emit ( " changed " )
class ArtistView ( Gtk . ScrolledWindow ) :
def __init__ ( self , client , settings , emitter , genre_select ) :
2020-01-11 13:25:15 +03:00
Gtk . ScrolledWindow . __init__ ( self )
self . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
#adding vars
self . client = client
2020-02-26 01:37:27 +03:00
self . settings = settings
2020-02-25 00:31:39 +03:00
self . emitter = emitter
2020-02-27 22:05:48 +03:00
self . genre_select = genre_select
2020-01-11 13:25:15 +03:00
#artistStore
#(name)
self . store = Gtk . ListStore ( str )
self . store . set_sort_column_id ( 0 , Gtk . SortType . ASCENDING )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
2020-02-28 19:42:52 +03:00
self . treeview . set_search_column ( - 1 )
2020-02-26 01:37:27 +03:00
self . treeview . columns_autosize ( )
2020-01-11 13:25:15 +03:00
#artistSelection
self . selection = self . treeview . get_selection ( )
2020-02-01 14:45:34 +03:00
self . selection . set_mode ( Gtk . SelectionMode . MULTIPLE )
2020-01-11 13:25:15 +03:00
#Old Name Column
renderer_text = Gtk . CellRendererText ( )
2020-02-28 16:27:58 +03:00
self . column_name = Gtk . TreeViewColumn ( " " , renderer_text , text = 0 )
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 . column_name . set_sort_column_id ( 0 )
self . treeview . append_column ( self . column_name )
2020-02-02 20:08:59 +03:00
#connect
self . treeview . connect ( " enter-notify-event " , self . on_enter_event )
2020-02-26 01:37:27 +03:00
self . settings . connect ( " changed::show-all-artists " , self . refresh )
2020-02-25 00:31:39 +03:00
self . update_signal = self . emitter . connect ( " update " , self . refresh )
2020-02-02 20:08:59 +03:00
2020-01-11 13:25:15 +03:00
self . add ( self . treeview )
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-02-27 22:05:48 +03:00
genre = self . genre_select . get_value ( )
if genre == None :
artists = self . client . list ( self . settings . get_artist_type ( ) )
else :
artists = self . client . list ( self . settings . get_artist_type ( ) , " genre " , genre )
for artist in artists :
2020-02-25 00:31:39 +03:00
self . store . append ( [ artist ] )
self . selection . set_mode ( Gtk . SelectionMode . MULTIPLE )
2020-01-11 13:25:15 +03:00
2020-02-01 16:36:57 +03:00
def get_selected_artists ( self ) :
paths = self . selection . get_selected_rows ( ) [ 1 ]
artists = [ ]
for path in paths :
treeiter = self . store . get_iter ( path )
if not treeiter == None :
selected_artist = self . store . get_value ( treeiter , 0 )
artists . append ( selected_artist )
return artists
2020-02-02 20:08:59 +03:00
def on_enter_event ( self , widget , event ) :
self . treeview . grab_focus ( )
2020-02-01 14:45:34 +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-01-11 13:25:15 +03:00
2020-02-01 14:45:34 +03:00
#cover, display_label, tooltip(titles), album, year, artist
self . store = Gtk . ListStore ( GdkPixbuf . Pixbuf , 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 )
self . set_text_column ( 1 )
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
self . album_change = self . connect ( " selection-changed " , self . on_album_selection_change )
self . album_item_activated = self . connect ( " item-activated " , self . on_album_item_activated )
self . connect ( " button-press-event " , self . on_album_view_button_press_event )
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-02-02 20:08:59 +03:00
self . connect ( " motion-notify-event " , self . on_move_event )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
@GObject.Signal
def stopped ( self ) :
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-02-01 14:45:34 +03:00
self . set_tooltip_column ( 2 )
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 " ) :
self . store . set_sort_column_id ( 4 , Gtk . SortType . ASCENDING )
else :
self . store . set_sort_column_id ( 1 , Gtk . SortType . ASCENDING )
2020-02-01 14:45:34 +03:00
def gen_tooltip ( self , album , artist , year ) :
2020-02-26 01:37:27 +03:00
songs = self . client . find ( " album " , album , " date " , year , self . settings . get_artist_type ( ) , artist )
2020-02-01 14:45:34 +03:00
length = float ( 0 )
for song in songs :
try :
dura = float ( song [ " duration " ] )
except :
dura = 0.0
length = length + dura
length_human_readable = str ( datetime . timedelta ( seconds = int ( length ) ) )
tooltip = ( _ ( " %(total_tracks)i titles ( %(total_length)s ) " ) % { " total_tracks " : len ( songs ) , " total_length " : length_human_readable } )
return tooltip
def populate ( self , artists ) :
2020-02-25 00:31:39 +03:00
self . stop_flag = False
size = self . settings . get_int ( " album-cover " )
albums = [ ]
2020-02-27 22:05:48 +03:00
genre = self . genre_select . get_value ( )
2020-02-01 14:45:34 +03:00
for artist in artists :
2020-02-27 22:05:48 +03:00
if genre == None :
album_candidates = self . client . list ( " album " , self . settings . get_artist_type ( ) , artist )
else :
album_candidates = self . client . list ( " album " , self . settings . get_artist_type ( ) , artist , " genre " , genre )
for album in album_candidates :
2020-02-26 01:37:27 +03:00
albums . append ( { " artist " : artist , " album " : album , " year " : self . client . list ( " date " , " album " , album , self . settings . get_artist_type ( ) , artist ) [ 0 ] } )
2020-02-28 18:03:45 +03:00
if self . settings . get_boolean ( " sort-albums-by-year " ) :
albums = sorted ( albums , key = lambda k : k [ ' year ' ] )
else :
albums = sorted ( albums , key = lambda k : k [ ' album ' ] )
2020-02-25 00:31:39 +03:00
for album in albums :
if self . client . connected ( ) and not self . stop_flag : #self.get_visible() and self.client.connected() and
2020-02-26 01:37:27 +03:00
songs = self . client . find ( " album " , album [ " album " ] , " date " , album [ " year " ] , self . settings . get_artist_type ( ) , album [ " artist " ] )
2020-02-25 00:31:39 +03:00
if songs == [ ] :
song_file = None
2020-02-01 14:45:34 +03:00
else :
2020-02-25 00:31:39 +03:00
song_file = songs [ 0 ] [ " file " ]
cover = Cover ( client = self . client , lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = song_file )
img = cover . get_pixbuf ( size )
if album [ " year " ] == " " :
self . store . append ( [ img , album [ " album " ] , self . gen_tooltip ( album [ " album " ] , album [ " artist " ] , album [ " year " ] ) , album [ " album " ] , album [ " year " ] , album [ " artist " ] ] )
else :
self . store . append ( [ img , album [ " album " ] + " ( " + album [ " year " ] + " ) " , self . gen_tooltip ( album [ " album " ] , album [ " artist " ] , album [ " year " ] ) , album [ " album " ] , album [ " year " ] , album [ " artist " ] ] )
while Gtk . events_pending ( ) :
Gtk . main_iteration_do ( True )
else :
self . store . clear ( )
self . emit ( " stopped " )
break
self . stop_flag = True
2020-02-01 14:45:34 +03:00
2020-02-03 01:10:33 +03:00
def scroll_to_selected_album ( self ) :
2020-02-01 14:45:34 +03:00
songid = self . client . status ( ) [ " songid " ]
song = self . client . playlistid ( songid ) [ 0 ]
2020-02-03 01:10:33 +03:00
self . handler_block ( self . album_change )
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 )
treeiter = self . store . get_iter ( path )
if self . store . get_value ( treeiter , 3 ) == 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-02-03 01:10:33 +03:00
self . handler_unblock ( self . album_change )
2020-02-01 14:45:34 +03:00
def on_album_view_button_press_event ( self , widget , event ) :
path = widget . get_path_at_pos ( int ( event . x ) , int ( event . y ) )
if not path == None :
if not event . button == 1 :
treeiter = self . store . get_iter ( path )
selected_album = self . store . get_value ( treeiter , 3 )
selected_album_year = self . store . get_value ( treeiter , 4 )
selected_artist = self . store . get_value ( treeiter , 5 )
if event . button == 2 :
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , True )
elif event . button == 3 :
if self . client . connected ( ) :
2020-02-26 01:37:27 +03:00
album = AlbumDialog ( self . window , self . client , self . settings , selected_album , selected_artist , selected_album_year )
2020-02-01 14:45:34 +03:00
response = album . run ( )
if response == Gtk . ResponseType . OK :
self . select_path ( path )
elif response == Gtk . ResponseType . ACCEPT :
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , True )
elif response == Gtk . ResponseType . YES :
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , False , True )
album . destroy ( )
def on_album_selection_change ( self , widget ) :
paths = widget . get_selected_items ( )
if not len ( paths ) == 0 :
treeiter = self . store . get_iter ( paths [ 0 ] )
selected_album = self . store . get_value ( treeiter , 3 )
selected_album_year = self . store . get_value ( treeiter , 4 )
selected_artist = self . store . get_value ( treeiter , 5 )
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , False )
def on_album_item_activated ( self , widget , path ) :
treeiter = self . store . get_iter ( path )
selected_album = self . store . get_value ( treeiter , 3 )
selected_album_year = self . store . get_value ( treeiter , 4 )
selected_artist = self . store . get_value ( treeiter , 5 )
self . client . album_to_playlist ( selected_album , selected_artist , selected_album_year , False , True )
2020-02-02 20:08:59 +03:00
def on_move_event ( self , widget , event ) :
self . grab_focus ( )
2020-02-01 14:45:34 +03:00
class AlbumView ( Gtk . ScrolledWindow ) :
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 . ScrolledWindow . __init__ ( self )
self . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
#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-02-01 14:45:34 +03:00
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
self . iconview . connect ( " stopped " , self . update )
self . add ( self . iconview )
def update ( self , * args ) :
if self . client . connected ( ) :
self . iconview . populate ( self . artists )
2020-02-01 14:45:34 +03:00
2020-02-01 16:12:56 +03:00
def clear ( self ) :
2020-02-25 00:31:39 +03:00
self . artists = [ ]
if self . iconview . stop_flag :
self . iconview . store . clear ( )
else :
self . iconview . stop_flag = True
2020-02-01 16:12:56 +03:00
def refresh ( self , artists ) :
2020-02-25 00:31:39 +03:00
if artists != self . artists :
self . artists = artists
if self . iconview . stop_flag :
self . iconview . store . clear ( )
self . iconview . emit ( " stopped " )
else :
self . iconview . stop_flag = True
2020-02-01 14:45:34 +03:00
2020-02-03 01:10:33 +03:00
def scroll_to_selected_album ( self ) :
self . iconview . scroll_to_selected_album ( )
2020-01-11 13:25:15 +03:00
class TrackView ( Gtk . Box ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter , window ) :
2020-01-11 13:25:15 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL )
#adding vars
self . client = client
2020-02-26 01:37:27 +03:00
self . settings = settings
2020-02-25 00:31:39 +03:00
self . emitter = emitter
2020-02-03 01:10:33 +03:00
self . window = window
2020-01-11 13:25:15 +03:00
self . hovered_songpos = None
self . song_file = None
2020-02-25 00:31:39 +03:00
self . playlist_version = None
2020-01-11 13:25:15 +03:00
#Store
#(track, title, artist, album, duration, file)
self . store = Gtk . ListStore ( str , str , str , str , str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_search_column ( - 1 )
self . treeview . columns_autosize ( )
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
self . selection = self . treeview . get_selection ( )
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
#Column
renderer_text = Gtk . CellRendererText ( )
self . column_track = Gtk . TreeViewColumn ( _ ( " No " ) , renderer_text , text = 0 )
self . column_track . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
2020-02-29 01:40:01 +03:00
self . column_track . set_property ( " resizable " , False )
2020-01-11 13:25:15 +03:00
self . treeview . 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 " , True )
self . treeview . 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 " , True )
self . treeview . append_column ( self . column_artist )
self . column_duration = Gtk . TreeViewColumn ( _ ( " Length " ) , renderer_text , text = 4 )
self . column_duration . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
2020-02-29 01:40:01 +03:00
self . column_duration . set_property ( " resizable " , False )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_duration )
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
scroll . add ( self . treeview )
#cover
self . cover = Gtk . Image . new ( )
self . cover . set_from_pixbuf ( Cover ( client = self . client , lib_path = self . settings . get_value ( " paths " ) [ self . settings . get_int ( " active-profile " ) ] , song_file = None ) . get_pixbuf ( self . settings . get_int ( " track-cover " ) ) ) #set to fallback cover
2020-02-03 01:10:33 +03:00
cover_event_box = Gtk . EventBox ( )
cover_event_box . add ( self . cover )
2020-01-11 13:25:15 +03:00
#audio infos
audio = AudioType ( self . client )
2020-01-27 22:27:35 +03:00
#playlist info
self . playlist_info = Gtk . Label ( )
self . playlist_info . set_xalign ( 0 )
self . playlist_info . set_ellipsize ( Pango . EllipsizeMode . END )
#status bar
status_bar = Gtk . Box ( orientation = Gtk . Orientation . HORIZONTAL , spacing = 10 )
2020-02-03 01:55:16 +03:00
status_bar . set_margin_start ( 4 )
status_bar . set_margin_end ( 4 )
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 . treeview . connect ( " motion-notify-event " , self . on_move_event )
2020-02-28 19:32:15 +03:00
self . treeview . connect ( " leave-notify-event " , self . on_focus_out_event )
2020-02-25 00:31:39 +03:00
self . treeview . connect ( " focus-out-event " , self . on_focus_out_event )
2020-01-11 13:25:15 +03:00
self . key_press_event = self . treeview . connect ( " key-press-event " , self . on_key_press_event )
2020-02-03 01:10:33 +03:00
cover_event_box . connect ( " button-press-event " , self . on_button_press_event )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
self . playlist_changed = self . emitter . connect ( " playlist " , self . on_playlist_changed )
self . player_changed = self . emitter . connect ( " player " , self . on_player_changed )
2020-01-11 13:25:15 +03:00
#packing
2020-02-03 01:10:33 +03:00
self . pack_start ( cover_event_box , False , False , 0 )
2020-01-11 13:25:15 +03:00
self . pack_start ( scroll , True , True , 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-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 )
self . treeview . scroll_to_cell ( path )
def refresh_cover ( self ) :
2020-01-11 13:25:15 +03:00
try :
song_file = self . client . currentsong ( ) [ " file " ]
except :
song_file = None
if not song_file == self . song_file :
self . cover . set_from_pixbuf ( Cover ( client = self . client , 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 " ) ) )
self . song_file = song_file
return True
2020-01-29 19:29:47 +03:00
def refresh_playlist_info ( self ) :
songs = self . client . playlistinfo ( )
if not songs == [ ] :
whole_length = float ( 0 )
for song in songs :
try :
dura = float ( song [ " duration " ] )
except :
dura = 0.0
whole_length = whole_length + dura
whole_length_human_readable = str ( datetime . timedelta ( seconds = int ( whole_length ) ) )
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-02-01 14:45:34 +03:00
try :
song = self . client . status ( ) [ " song " ]
path = Gtk . TreePath ( int ( song ) )
2020-02-29 10:23:42 +03:00
self . selection . select_path ( path )
2020-02-01 14:45:34 +03:00
except :
self . selection . unselect_all ( )
self . refresh_cover ( )
2020-02-25 00:31:39 +03:00
def clear ( self , * args ) :
self . playlist_info . set_text ( " " )
self . store . clear ( )
self . playlist_version = None
self . cover . set_from_pixbuf ( Cover ( client = self . client , 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-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
if not self . hovered_songpos == None :
try :
self . client . delete ( self . hovered_songpos ) #bad song index possible
2020-02-25 14:07:37 +03:00
self . store . remove ( self . store . get_iter ( self . hovered_songpos ) )
self . playlist_version = self . client . status ( ) [ " playlist " ]
2020-01-11 13:25:15 +03:00
except :
self . hovered_songpos == None
self . treeview . handler_unblock ( self . key_press_event )
def on_move_event ( self , widget , event ) :
2020-02-01 17:37:13 +03:00
treeiter = self . selection . get_selected ( ) [ 1 ]
if not treeiter == None :
self . treeview . grab_focus ( )
return_tuple = self . treeview . get_path_at_pos ( int ( event . x ) , int ( event . y ) )
if not return_tuple == None :
self . hovered_songpos = return_tuple [ 0 ]
else :
self . hovered_songpos = None
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
def on_focus_out_event ( self , widget , event ) :
self . refresh_selection ( )
2020-01-11 13:25:15 +03:00
self . hovered_songpos = None
2020-02-25 15:59:43 +03:00
def on_row_activated ( self , widget , path , view_column ) : #TODO
2020-01-11 13:25:15 +03:00
treeiter = self . store . get_iter ( path )
selected_title = self . store . get_path ( treeiter )
self . client . play ( selected_title )
2020-02-03 01:10:33 +03:00
def on_button_press_event ( self , widget , event ) :
if self . client . connected ( ) :
song = self . client . currentsong ( )
if not song == { } :
try :
2020-02-26 01:37:27 +03:00
artist = song [ self . settings . get_artist_type ( ) ]
2020-02-03 01:10:33 +03:00
except :
artist = " "
try :
album = song [ " album " ]
except :
album = " "
try :
album_year = song [ " date " ]
except :
album_year = " "
if event . button == 1 :
self . client . album_to_playlist ( album , artist , album_year , False )
elif event . button == 2 :
self . client . album_to_playlist ( album , artist , album_year , True )
elif event . button == 3 :
2020-02-26 01:37:27 +03:00
album_dialog = AlbumDialog ( self . window , self . client , self . settings , album , artist , album_year )
2020-02-03 01:10:33 +03:00
response = album_dialog . run ( )
if response == Gtk . ResponseType . OK :
self . client . album_to_playlist ( album , artist , album_year , False )
elif response == Gtk . ResponseType . ACCEPT :
self . client . album_to_playlist ( album , artist , album_year , True )
elif response == Gtk . ResponseType . YES :
self . client . album_to_playlist ( album , artist , album_year , False , True )
album_dialog . destroy ( )
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 ( " " )
for song in songs :
try :
title = song [ " title " ]
except :
title = _ ( " Unknown Title " )
try :
track = song [ " track " ] . zfill ( 2 )
except :
track = " 00 "
try :
artist = song [ " artist " ]
except :
artist = _ ( " Unknown Artist " )
try :
album = song [ " album " ]
except :
album = _ ( " Unknown Album " )
try :
dura = float ( song [ " duration " ] )
except :
dura = 0.0
duration = str ( datetime . timedelta ( seconds = int ( dura ) ) )
try :
treeiter = self . store . get_iter ( song [ " pos " ] )
2020-02-25 14:07:37 +03:00
self . store . set ( treeiter , 0 , track , 1 , title , 2 , artist , 3 , album , 4 , duration , 5 , song [ " file " ] )
2020-02-25 00:31:39 +03:00
except :
2020-02-25 14:07:37 +03:00
self . store . append ( [ track , title , artist , album , duration , song [ " file " ] ] )
2020-02-25 00:31:39 +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 ( )
self . refresh_selection ( )
self . playlist_version = self . client . status ( ) [ " playlist " ]
def on_player_changed ( self , * args ) :
2020-02-25 15:59:43 +03:00
if not self . client . song_to_delete == " " : #TODO should be in Client class
2020-02-25 00:31:39 +03:00
status = self . client . status ( )
if not status [ " song " ] == " 0 " and self . client . playlistinfo ( ) [ 0 ] [ " file " ] == self . client . song_to_delete :
self . client . delete ( 0 )
else :
self . refresh_selection ( )
self . client . song_to_delete = " "
else :
self . refresh_selection ( )
2020-01-11 13:25:15 +03:00
class Browser ( Gtk . Box ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter , window ) :
2020-02-27 22:05:48 +03:00
Gtk . Box . __init__ ( self , orientation = Gtk . Orientation . VERTICAL , spacing = 4 )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
2020-02-25 00:31:39 +03:00
self . emitter = emitter
2020-01-11 13:25:15 +03:00
self . settings = settings
2020-01-18 00:13:58 +03:00
self . window = window
2020-01-11 13:25:15 +03:00
#widgets
2020-02-27 22:05:48 +03:00
self . genre_select = GenreSelect ( self . client , self . settings , self . emitter )
2020-02-29 01:23:53 +03:00
self . genre_select . set_margin_start ( 2 )
self . genre_select . set_margin_end ( 2 )
self . genre_select . set_margin_top ( 2 )
2020-02-27 22:05:48 +03:00
self . artist_list = ArtistView ( self . client , self . settings , self . emitter , self . genre_select )
self . album_list = AlbumView ( self . client , self . settings , self . genre_select , self . window )
2020-02-25 00:31:39 +03:00
self . title_list = TrackView ( self . client , self . settings , self . emitter , self . window )
2020-01-11 13:25:15 +03:00
#connect
self . artist_change = self . artist_list . selection . connect ( " changed " , self . on_artist_selection_change )
2020-02-29 01:23:53 +03:00
self . settings . connect ( " changed::show-genre-filter " , self . on_settings_changed )
2020-01-11 13:25:15 +03:00
#packing
2020-02-29 01:23:53 +03:00
self . vbox = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL , spacing = 4 )
if self . settings . get_boolean ( " show-genre-filter " ) :
self . vbox . pack_start ( self . genre_select , False , False , 0 )
self . vbox . pack_start ( self . artist_list , True , True , 0 )
2020-01-11 13:25:15 +03:00
self . paned1 = Gtk . Paned . new ( Gtk . Orientation . HORIZONTAL )
2020-02-02 20:08:59 +03:00
self . paned1 . set_wide_handle ( True )
2020-01-11 13:25:15 +03:00
self . paned2 = Gtk . Paned . new ( Gtk . Orientation . HORIZONTAL )
2020-02-02 20:08:59 +03:00
self . paned2 . set_wide_handle ( True )
2020-02-29 01:23:53 +03:00
self . paned1 . pack1 ( self . vbox , False , False )
2020-01-11 13:25:15 +03:00
self . paned1 . pack2 ( self . album_list , True , False )
self . paned2 . pack1 ( self . paned1 , True , False )
self . paned2 . pack2 ( self . title_list , False , False )
self . load_settings ( )
self . pack_start ( self . paned2 , True , True , 0 )
def save_settings ( self ) :
self . settings . set_int ( " paned1 " , self . paned1 . get_position ( ) )
self . settings . set_int ( " paned2 " , self . paned2 . get_position ( ) )
def load_settings ( self ) :
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 ) :
self . artist_list . selection . handler_block ( self . artist_change )
self . artist_list . clear ( )
self . artist_list . selection . handler_unblock ( self . artist_change )
self . album_list . clear ( )
self . title_list . clear ( )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
def go_home ( self , * args ) : #TODO
2020-01-11 13:25:15 +03:00
try :
songid = self . client . status ( ) [ " songid " ]
song = self . client . playlistid ( songid ) [ 0 ]
row_num = len ( self . artist_list . store )
for i in range ( 0 , row_num ) :
path = Gtk . TreePath ( i )
treeiter = self . artist_list . store . get_iter ( path )
2020-02-26 01:37:27 +03:00
if self . artist_list . store . get_value ( treeiter , 0 ) == song [ self . settings . get_artist_type ( ) ] :
2020-02-01 14:45:34 +03:00
if not self . artist_list . selection . iter_is_selected ( treeiter ) :
2020-02-25 00:31:39 +03:00
self . artist_list . selection . handler_block ( self . artist_change )
2020-02-01 14:45:34 +03:00
self . artist_list . selection . unselect_all ( )
2020-02-25 00:31:39 +03:00
self . artist_list . selection . handler_unblock ( self . artist_change )
2020-02-25 15:59:43 +03:00
self . artist_list . treeview . set_cursor ( path , None , False )
2020-01-11 13:25:15 +03:00
break
2020-02-03 01:10:33 +03:00
self . album_list . scroll_to_selected_album ( )
2020-01-11 13:25:15 +03:00
except :
2020-02-01 14:45:34 +03:00
pass
self . title_list . scroll_to_selected_title ( )
2020-01-11 13:25:15 +03:00
2020-02-01 16:36:57 +03:00
def on_artist_selection_change ( self , * args ) :
artists = self . artist_list . get_selected_artists ( )
2020-02-01 14:45:34 +03:00
self . album_list . refresh ( artists )
2020-01-11 13:25:15 +03:00
2020-02-29 01:23:53 +03:00
def on_settings_changed ( self , * args ) :
if self . settings . get_boolean ( " show-genre-filter " ) :
self . vbox . pack_start ( self . genre_select , False , False , 0 )
self . vbox . reorder_child ( self . genre_select , 0 )
self . genre_select . show_all ( )
else :
self . genre_select . deactivate ( )
self . vbox . remove ( self . genre_select )
2020-01-11 13:25:15 +03:00
class ProfileSettings ( Gtk . Grid ) :
def __init__ ( self , parent , settings ) :
Gtk . Grid . __init__ ( self )
2020-02-02 20:08:59 +03:00
self . set_row_spacing ( 4 )
self . set_column_spacing ( 4 )
self . set_property ( " border-width " , 4 )
2020-01-11 13:25:15 +03:00
#adding vars
self . settings = settings
#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 ) )
self . profile_entry = Gtk . Entry ( )
self . host_entry = Gtk . Entry ( )
self . port_entry = IntEntry ( 0 , 0 , 65535 )
2020-02-01 15:27:46 +03:00
self . password_entry = Gtk . Entry ( )
self . password_entry . set_visibility ( False )
2020-01-11 13:25:15 +03:00
self . path_select_button = Gtk . Button ( label = _ ( " Select " ) , image = Gtk . Image ( stock = Gtk . STOCK_OPEN ) )
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 )
port_label = Gtk . Label ( label = _ ( " Port: " ) )
port_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-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 )
self . attach_next_to ( port_label , host_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-02-01 15:27:46 +03:00
self . attach_next_to ( password_label , port_label , Gtk . PositionType . BOTTOM , 1 , 1 )
self . attach_next_to ( path_label , password_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-01-11 13:25:15 +03:00
self . attach_next_to ( self . profiles_combo , profiles_label , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( add_button , self . profiles_combo , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( delete_button , add_button , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( self . profile_entry , profile_label , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( self . host_entry , host_label , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( self . port_entry , port_label , Gtk . PositionType . RIGHT , 1 , 1 )
2020-02-01 15:27:46 +03:00
self . attach_next_to ( self . password_entry , password_label , Gtk . PositionType . RIGHT , 1 , 1 )
2020-01-11 13:25:15 +03:00
self . attach_next_to ( self . path_select_button , path_label , Gtk . PositionType . RIGHT , 1 , 1 )
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-01-11 13:25:15 +03:00
def on_path_select_button_clicked ( self , widget , parent ) :
dialog = Gtk . FileChooserDialog ( title = _ ( " Choose directory " ) , transient_for = parent , action = Gtk . FileChooserAction . SELECT_FOLDER )
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 ( ) ] )
response = dialog . run ( )
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-02-02 16:20:25 +03:00
self . path_select_button . set_tooltip_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-01-11 13:25:15 +03:00
self . path_select_button . set_tooltip_text ( self . settings . get_value ( " paths " ) [ active ] )
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
class GeneralSettings ( Gtk . Grid ) :
def __init__ ( self , settings ) :
Gtk . Grid . __init__ ( self )
2020-02-02 20:08:59 +03:00
self . set_row_spacing ( 4 )
self . set_column_spacing ( 4 )
self . set_property ( " border-width " , 4 )
2020-01-11 13:25:15 +03:00
#adding vars
self . settings = settings
#widgets
track_cover_label = Gtk . Label ( label = _ ( " Main cover size: " ) )
track_cover_label . set_xalign ( 1 )
album_cover_label = Gtk . Label ( label = _ ( " Album-view cover size: " ) )
album_cover_label . set_xalign ( 1 )
track_cover_size = IntEntry ( self . settings . get_int ( " track-cover " ) , 100 , 1200 )
album_cover_size = IntEntry ( self . settings . get_int ( " album-cover " ) , 50 , 600 )
2020-01-28 21:59:14 +03:00
icon_size_label = Gtk . Label ( label = _ ( " Button icon size (restart required): " ) )
icon_size_label . set_xalign ( 1 )
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-01-11 13:25:15 +03:00
show_stop = Gtk . CheckButton ( label = _ ( " Show stop button " ) )
show_stop . set_active ( self . settings . get_boolean ( " show-stop " ) )
2020-02-29 01:23:53 +03:00
show_genre_filter = Gtk . CheckButton ( label = _ ( " Show genre filter " ) )
show_genre_filter . set_active ( self . settings . get_boolean ( " show-genre-filter " ) )
2020-01-18 00:13:58 +03:00
show_album_view_tooltips = Gtk . CheckButton ( label = _ ( " Show tooltips in album view " ) )
2020-01-11 13:25:15 +03:00
show_album_view_tooltips . set_active ( self . settings . get_boolean ( " show-album-view-tooltips " ) )
2020-02-28 18:03:45 +03:00
sort_albums_by_year = Gtk . CheckButton ( label = _ ( " Sort albums by year " ) )
sort_albums_by_year . set_active ( self . settings . get_boolean ( " sort-albums-by-year " ) )
2020-02-26 01:37:27 +03:00
show_all_artists = Gtk . CheckButton ( label = _ ( " Show all artists " ) )
show_all_artists . set_active ( self . settings . get_boolean ( " show-all-artists " ) )
2020-01-11 13:25:15 +03:00
send_notify = Gtk . CheckButton ( label = _ ( " Send notification on title change " ) )
send_notify . set_active ( self . settings . get_boolean ( " send-notify " ) )
stop_on_quit = Gtk . CheckButton ( label = _ ( " Stop playback on quit " ) )
stop_on_quit . set_active ( self . settings . get_boolean ( " stop-on-quit " ) )
add_album = Gtk . CheckButton ( label = _ ( " Play selected album after current title " ) )
add_album . set_active ( self . settings . get_boolean ( " add-album " ) )
#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
show_stop . connect ( " toggled " , self . on_toggled , " show-stop " )
2020-02-29 01:23:53 +03:00
show_genre_filter . connect ( " toggled " , self . on_toggled , " show-genre-filter " )
2020-01-11 13:25:15 +03:00
show_album_view_tooltips . connect ( " toggled " , self . on_toggled , " show-album-view-tooltips " )
2020-02-28 18:03:45 +03:00
sort_albums_by_year . connect ( " toggled " , self . on_toggled , " sort-albums-by-year " )
2020-02-26 01:37:27 +03:00
show_all_artists . connect ( " toggled " , self . on_toggled , " show-all-artists " )
2020-01-11 13:25:15 +03:00
send_notify . connect ( " toggled " , self . on_toggled , " send-notify " )
stop_on_quit . connect ( " toggled " , self . on_toggled , " stop-on-quit " )
add_album . connect ( " toggled " , self . on_toggled , " add-album " )
#packing
self . add ( track_cover_label )
self . attach_next_to ( album_cover_label , track_cover_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-01-28 21:59:14 +03:00
self . attach_next_to ( icon_size_label , album_cover_label , Gtk . PositionType . BOTTOM , 1 , 1 )
2020-01-11 13:25:15 +03:00
self . attach_next_to ( track_cover_size , track_cover_label , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( album_cover_size , album_cover_label , Gtk . PositionType . RIGHT , 1 , 1 )
2020-01-28 21:59:14 +03:00
self . attach_next_to ( icon_size_combo , icon_size_label , Gtk . PositionType . RIGHT , 1 , 1 )
self . attach_next_to ( show_stop , icon_size_label , Gtk . PositionType . BOTTOM , 2 , 1 )
2020-02-29 01:23:53 +03:00
self . attach_next_to ( show_genre_filter , show_stop , Gtk . PositionType . BOTTOM , 2 , 1 )
self . attach_next_to ( show_album_view_tooltips , show_genre_filter , Gtk . PositionType . BOTTOM , 2 , 1 )
2020-02-28 18:03:45 +03:00
self . attach_next_to ( sort_albums_by_year , show_album_view_tooltips , Gtk . PositionType . BOTTOM , 2 , 1 )
self . attach_next_to ( show_all_artists , sort_albums_by_year , Gtk . PositionType . BOTTOM , 2 , 1 )
2020-02-26 01:37:27 +03:00
self . attach_next_to ( send_notify , show_all_artists , Gtk . PositionType . BOTTOM , 2 , 1 )
2020-01-11 13:25:15 +03:00
self . attach_next_to ( add_album , send_notify , Gtk . PositionType . BOTTOM , 2 , 1 )
self . attach_next_to ( stop_on_quit , add_album , Gtk . PositionType . BOTTOM , 2 , 1 )
def on_int_changed ( self , widget , key ) :
self . settings . set_int ( key , widget . get_int ( ) )
def on_toggled ( self , widget , key ) :
self . settings . set_boolean ( key , widget . get_active ( ) )
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-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
self . settings = settings
#widgets
general = GeneralSettings ( self . settings )
profiles = ProfileSettings ( parent , self . settings )
#packing
tabs = Gtk . Notebook ( )
tabs . append_page ( general , Gtk . Label ( label = _ ( " General " ) ) )
tabs . append_page ( profiles , Gtk . Label ( label = _ ( " Profiles " ) ) )
self . vbox . pack_start ( tabs , True , True , 0 ) #vbox default widget of dialogs
2020-01-19 02:00:40 +03:00
self . vbox . set_spacing ( 6 )
2020-01-11 13:25:15 +03:00
self . show_all ( )
class ClientControl ( Gtk . ButtonBox ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter ) :
2020-02-02 20:08:59 +03:00
Gtk . ButtonBox . __init__ ( self , spacing = 4 )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
self . settings = settings
2020-02-25 00:31:39 +03:00
self . emitter = emitter
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-01-28 21:59:14 +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 ) )
2020-02-28 19:32:15 +03:00
self . stop_button . set_can_focus ( False )
2020-01-28 21:59:14 +03:00
self . prev_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-skip-backward-symbolic " , self . icon_size ) )
2020-02-28 19:32:15 +03:00
self . prev_button . set_can_focus ( False )
2020-01-28 21:59:14 +03:00
self . next_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " media-skip-forward-symbolic " , self . icon_size ) )
2020-02-28 19:32:15 +03:00
self . next_button . set_can_focus ( False )
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-02-25 00:31:39 +03:00
self . player_changed = self . emitter . connect ( " player " , self . update )
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-02-25 00:31:39 +03:00
def update ( self , * args ) :
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
self . update ( )
def on_stop_clicked ( self , widget ) :
if self . client . connected ( ) :
self . client . stop ( )
self . update ( )
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 ) :
def __init__ ( self , client ) :
Gtk . Box . __init__ ( self )
#adding vars
self . client = client
2020-02-16 14:20:38 +03:00
self . seek_time = " 10 " #seek increment in seconds
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-01-19 02:00:40 +03:00
self . elapsed . set_width_chars ( 7 )
2020-01-11 13:25:15 +03:00
self . rest = Gtk . Label ( )
2020-01-19 02:00:40 +03:00
self . rest . set_width_chars ( 8 )
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 )
self . scale . set_draw_value ( False )
2020-02-02 20:08:59 +03:00
self . scale . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
2020-02-16 14:20:38 +03:00
#event boxes
self . elapsed_event_box = Gtk . EventBox ( )
self . rest_event_box = Gtk . EventBox ( )
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-01-11 13:25:15 +03:00
#connect
self . scale . connect ( " change-value " , self . seek )
2020-03-01 01:57:44 +03:00
self . scale . connect ( " scroll-event " , self . dummy ) #disable mouse wheel which caused some noise
2020-01-11 13:25:15 +03:00
#timeouts
GLib . timeout_add ( 100 , self . update )
#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-03-01 01:57:44 +03:00
def dummy ( self , * args ) :
return True
2020-01-11 13:25:15 +03:00
def seek ( self , range , scroll , value ) :
status = self . client . status ( )
2020-01-12 18:27:23 +03:00
duration = float ( status [ " duration " ] )
factor = ( value / 100 )
pos = ( duration * factor )
self . client . seekcur ( pos )
2020-01-11 13:25:15 +03:00
2020-02-16 14:20:38 +03:00
def on_elapsed_button_press_event ( self , widget , event ) :
if event . button == 1 :
self . client . seekcur ( " - " + self . seek_time )
elif event . button == 3 :
self . client . seekcur ( " + " + self . seek_time )
def on_rest_button_press_event ( self , widget , event ) :
if event . button == 1 :
self . client . seekcur ( " + " + self . seek_time )
elif event . button == 3 :
self . client . seekcur ( " - " + self . seek_time )
2020-01-11 13:25:15 +03:00
def update ( self ) :
try :
status = self . client . status ( )
duration = float ( status [ " duration " ] )
elapsed = float ( status [ " elapsed " ] )
fraction = ( elapsed / duration ) * 100
self . scale . set_value ( fraction )
self . elapsed . set_text ( str ( datetime . timedelta ( seconds = int ( elapsed ) ) ) )
self . rest . set_text ( " - " + str ( datetime . timedelta ( seconds = int ( duration - elapsed ) ) ) )
self . scale . set_sensitive ( True )
2020-02-16 14:20:38 +03:00
self . elapsed_event_box . set_sensitive ( True )
self . rest_event_box . set_sensitive ( True )
2020-01-11 13:25:15 +03:00
except :
self . scale . set_value ( 0.0 )
self . elapsed . set_text ( " 0:00:00 " )
self . rest . set_text ( " -0:00:00 " )
self . scale . set_sensitive ( False )
2020-02-16 14:20:38 +03:00
self . elapsed_event_box . set_sensitive ( False )
self . rest_event_box . set_sensitive ( False )
2020-01-11 13:25:15 +03:00
return True
class PlaybackOptions ( Gtk . Box ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter ) :
2020-01-11 13:25:15 +03:00
Gtk . Box . __init__ ( self )
#adding vars
self . client = client
2020-01-28 21:59:14 +03:00
self . settings = settings
2020-02-25 00:31:39 +03:00
self . emitter = emitter
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-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-02-28 19:32:15 +03:00
self . random . set_can_focus ( False )
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-02-28 19:32:15 +03:00
self . repeat . set_can_focus ( False )
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-02-28 19:32:15 +03:00
self . single . set_can_focus ( False )
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-02-28 19:32:15 +03:00
self . consume . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
self . consume . set_tooltip_text ( _ ( " Consume mode " ) )
self . volume = Gtk . VolumeButton ( )
2020-02-28 19:32:15 +03:00
self . volume . set_can_focus ( False )
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-02-25 00:31:39 +03:00
self . options_changed = self . emitter . connect ( " options " , self . options_update )
self . mixer_changed = self . emitter . connect ( " mixer " , self . mixer_update )
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-02-25 00:31:39 +03:00
def options_update ( 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
def mixer_update ( self , * args ) :
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 )
class AudioType ( Gtk . EventBox ) :
def __init__ ( self , client ) :
Gtk . EventBox . __init__ ( self )
2020-02-02 20:08:59 +03:00
self . set_tooltip_text ( _ ( " Click to show additional information " ) )
2020-01-11 13:25:15 +03:00
#adding vars
self . client = client
#widgets
self . label = Gtk . Label ( )
self . label . set_xalign ( 1 )
2020-01-27 22:27:35 +03:00
self . label . set_ellipsize ( Pango . EllipsizeMode . END )
2020-01-11 13:25:15 +03:00
self . popover = Gtk . Popover ( )
2020-02-08 18:27:52 +03:00
self . popover . set_relative_to ( self )
2020-01-11 13:25:15 +03:00
#Store
#(tag, value)
self . store = Gtk . ListStore ( str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_search_column ( - 1 )
sel = self . treeview . get_selection ( )
sel . set_mode ( Gtk . SelectionMode . NONE )
#Column
renderer_text = Gtk . CellRendererText ( )
self . column_tag = Gtk . TreeViewColumn ( _ ( " MPD-Tag " ) , renderer_text , text = 0 )
2020-02-08 18:27:52 +03:00
self . column_tag . set_property ( " resizable " , False )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_tag )
self . column_value = Gtk . TreeViewColumn ( _ ( " Value " ) , renderer_text , text = 1 )
2020-02-08 18:27:52 +03:00
self . column_value . set_property ( " resizable " , False )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_value )
#timeouts
GLib . timeout_add ( 1000 , self . update )
#connect
self . connect ( " button-press-event " , self . on_button_press_event )
2020-02-08 18:27:52 +03:00
#packing
self . popover . add ( self . treeview )
2020-01-11 13:25:15 +03:00
self . add ( self . label )
def update ( self ) :
if self . client . connected ( ) :
status = self . client . status ( )
try :
file_type = self . client . playlistinfo ( status [ " song " ] ) [ 0 ] [ " file " ] . split ( ' . ' ) [ - 1 ]
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 }
self . label . set_text ( string )
except :
self . label . set_text ( " - " )
else :
self . label . set_text ( " - " )
return True
def on_button_press_event ( self , widget , event ) :
2020-02-02 20:08:59 +03:00
if event . button == 1 or event . button == 2 or event . button == 3 :
2020-01-11 13:25:15 +03:00
try :
2020-02-08 18:27:52 +03:00
self . store . clear ( )
2020-01-11 13:25:15 +03:00
song = self . client . status ( ) [ " song " ]
tags = self . client . playlistinfo ( song ) [ 0 ]
for key in tags :
if key == " time " :
self . store . append ( [ key , str ( datetime . timedelta ( seconds = int ( tags [ key ] ) ) ) ] )
else :
self . store . append ( [ key , tags [ key ] ] )
self . popover . show_all ( )
2020-02-08 18:27:52 +03:00
self . treeview . queue_resize ( )
2020-01-11 13:25:15 +03:00
except :
pass
class ProfileSelect ( Gtk . ComboBoxText ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter ) :
2020-01-11 13:25:15 +03:00
Gtk . ComboBoxText . __init__ ( self )
#adding vars
self . client = client
self . settings = settings
2020-02-25 00:31:39 +03:00
self . emitter = emitter
2020-01-11 13:25:15 +03:00
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 )
self . settings . connect ( " changed::profiles " , self . on_settings_changed )
self . settings . connect ( " changed::hosts " , self . on_settings_changed )
self . settings . connect ( " changed::ports " , self . on_settings_changed )
2020-02-01 15:27:46 +03:00
self . settings . connect ( " changed::passwords " , self . on_settings_changed )
2020-01-11 13:25:15 +03:00
self . settings . connect ( " changed::paths " , self . on_settings_changed )
2020-01-27 00:23:21 +03:00
self . reload ( )
2020-01-11 13:25:15 +03:00
def reload ( self , * args ) :
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_settings_changed ( self , * args ) :
self . reload ( )
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 )
#adding vars
self . client = client
#Store
#(tag, value)
self . store = Gtk . ListStore ( str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_search_column ( - 1 )
sel = self . treeview . get_selection ( )
sel . set_mode ( Gtk . SelectionMode . NONE )
#Column
renderer_text = Gtk . CellRendererText ( )
self . column_tag = Gtk . TreeViewColumn ( _ ( " Tag " ) , renderer_text , text = 0 )
self . treeview . append_column ( self . column_tag )
self . column_value = Gtk . TreeViewColumn ( _ ( " Value " ) , renderer_text , text = 1 )
self . treeview . append_column ( self . column_value )
stats = self . client . stats ( )
for key in stats :
if key == " uptime " or key == " playtime " or key == " db_playtime " :
self . store . append ( [ key , str ( datetime . timedelta ( seconds = int ( stats [ key ] ) ) ) ] )
elif key == " db_update " :
self . store . append ( [ key , str ( datetime . datetime . fromtimestamp ( int ( stats [ key ] ) ) ) ] )
else :
self . store . append ( [ key , stats [ key ] ] )
self . vbox . pack_start ( self . treeview , True , True , 0 )
self . show_all ( )
2020-03-01 23:21:06 +03:00
class SearchWindow ( Gtk . Window ) :
def __init__ ( self , client ) :
Gtk . Window . __init__ ( self , title = _ ( " Search " ) )
self . set_icon_name ( " mpdevil " )
2020-01-11 13:25:15 +03:00
self . set_default_size ( 800 , 600 )
#adding vars
self . client = client
#scroll
scroll = Gtk . ScrolledWindow ( )
scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
#search entry
self . search_entry = Gtk . SearchEntry ( )
2020-03-01 23:21:06 +03:00
self . search_entry . set_margin_start ( 2 )
self . search_entry . set_margin_end ( 2 )
self . search_entry . set_margin_top ( 2 )
2020-01-11 13:25:15 +03:00
#label
self . label = Gtk . Label ( )
self . label . set_xalign ( 1 )
2020-03-01 23:21:06 +03:00
self . label . set_margin_end ( 4 )
self . label . set_margin_bottom ( 2 )
2020-01-11 13:25:15 +03:00
#Store
#(track, title, artist, album, duration, file)
self . store = Gtk . ListStore ( str , str , str , str , str , str )
#TreeView
self . treeview = Gtk . TreeView ( model = self . store )
self . treeview . set_search_column ( - 1 )
self . treeview . columns_autosize ( )
self . selection = self . treeview . get_selection ( )
self . selection . set_mode ( Gtk . SelectionMode . SINGLE )
#Column
renderer_text = Gtk . CellRendererText ( )
self . column_track = Gtk . TreeViewColumn ( _ ( " No " ) , renderer_text , text = 0 )
self . column_track . set_sizing ( Gtk . TreeViewColumnSizing . AUTOSIZE )
self . column_track . set_property ( " resizable " , False )
2020-01-27 23:03:13 +03:00
self . column_track . set_sort_column_id ( 0 )
2020-01-11 13:25:15 +03:00
self . treeview . 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 )
2020-01-27 23:03:13 +03:00
self . column_title . set_sort_column_id ( 1 )
2020-01-11 13:25:15 +03:00
self . treeview . 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 )
2020-01-27 23:03:13 +03:00
self . column_artist . set_sort_column_id ( 2 )
2020-01-11 13:25:15 +03:00
self . treeview . 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 )
2020-01-27 23:03:13 +03:00
self . column_album . set_sort_column_id ( 3 )
2020-01-11 13:25:15 +03:00
self . treeview . 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 )
2020-01-27 23:03:13 +03:00
self . column_time . set_sort_column_id ( 4 )
2020-01-11 13:25:15 +03:00
self . treeview . append_column ( self . column_time )
#connect
self . title_activated = self . treeview . connect ( " row-activated " , self . on_row_activated )
self . title_change = self . selection . connect ( " changed " , self . on_selection_change )
2020-01-18 00:13:58 +03:00
self . search_entry . connect ( " search-changed " , self . on_search_changed )
2020-01-11 13:25:15 +03:00
#packing
scroll . add ( self . treeview )
2020-03-01 23:21:06 +03:00
vbox = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL , spacing = 4 )
vbox . pack_start ( self . search_entry , False , False , 0 )
vbox . pack_start ( scroll , True , True , 0 )
vbox . pack_start ( self . label , False , False , 0 )
self . add ( vbox )
2020-01-11 13:25:15 +03:00
self . show_all ( )
def on_row_activated ( self , widget , path , view_column ) :
treeiter = self . store . get_iter ( path )
selected_title = self . store . get_value ( treeiter , 5 )
self . client . clear ( )
self . client . add ( selected_title )
2020-01-26 23:32:12 +03:00
self . client . play ( )
2020-01-11 13:25:15 +03:00
def on_selection_change ( self , widget ) :
treeiter = widget . get_selected ( ) [ 1 ]
if not treeiter == None :
selected_title = self . store . get_value ( treeiter , 5 )
self . client . add ( selected_title )
def on_search_changed ( self , widget ) :
self . store . clear ( )
2020-01-27 23:03:13 +03:00
for song in self . client . search ( " any " , self . search_entry . get_text ( ) ) :
2020-01-11 13:25:15 +03:00
try :
title = song [ " title " ]
except :
title = _ ( " Unknown Title " )
try :
track = song [ " track " ] . zfill ( 2 )
except :
track = " 00 "
try :
artist = song [ " artist " ]
except :
artist = _ ( " Unknown Artist " )
try :
album = song [ " album " ]
except :
album = _ ( " Unknown Album " )
2020-01-12 18:27:23 +03:00
try :
dura = float ( song [ " duration " ] )
except :
dura = 0.0
duration = str ( datetime . timedelta ( seconds = int ( dura ) ) )
2020-01-11 13:25:15 +03:00
self . store . append ( [ track , title , artist , album , duration , song [ " file " ] . replace ( " & " , " " ) ] )
self . label . set_text ( _ ( " Hits: %i " ) % ( len ( self . store ) ) )
2020-02-29 00:11:46 +03:00
class LyricsWindow ( Gtk . Window ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , client , settings , emitter ) :
2020-01-11 13:25:15 +03:00
Gtk . Window . __init__ ( self , title = _ ( " Lyrics " ) )
self . set_icon_name ( " mpdevil " )
self . set_default_size ( 450 , 800 )
#adding vars
self . settings = settings
2020-02-25 00:31:39 +03:00
self . client = client
self . emitter = emitter
2020-01-11 13:25:15 +03:00
self . current_song = { }
#widgets
self . scroll = Gtk . ScrolledWindow ( )
self . scroll . set_policy ( Gtk . PolicyType . AUTOMATIC , Gtk . PolicyType . AUTOMATIC )
self . label = Gtk . Label ( )
2020-02-29 10:56:46 +03:00
self . label . set_selectable ( True )
2020-01-11 13:25:15 +03:00
self . label . set_yalign ( 0 )
self . label . set_xalign ( 0 )
#connect
2020-02-25 00:31:39 +03:00
self . player_changed = self . emitter . connect ( " player " , self . update )
2020-02-29 00:11:46 +03:00
self . connect ( " destroy " , self . remove_handlers )
2020-01-11 13:25:15 +03:00
#packing
self . scroll . add ( self . label )
self . add ( self . scroll )
self . show_all ( )
2020-02-25 00:31:39 +03:00
self . update ( )
2020-01-11 13:25:15 +03:00
2020-02-29 00:11:46 +03:00
def remove_handlers ( self , * args ) :
self . emitter . disconnect ( self . player_changed )
2020-02-25 00:31:39 +03:00
def display_lyrics ( self ) :
GLib . idle_add ( self . label . set_text , _ ( " searching... " ) )
try :
text = self . getLyrics ( self . current_song [ " artist " ] , self . current_song [ " title " ] )
except :
text = _ ( " not found " )
GLib . idle_add ( self . label . set_text , text )
def update ( self , * args ) :
cs = self . client . currentsong ( )
if not cs == { } :
cs . pop ( " pos " ) #avoid unnecessary reloads caused by position change of current title
if cs != self . current_song :
self . current_song = cs
update_thread = threading . Thread ( target = self . display_lyrics , daemon = True )
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 _
singer = singer . replace ( ' ' , ' _ ' )
song = song . replace ( ' ' , ' _ ' )
r = requests . get ( ' http://lyrics.wikia.com/ {0} : {1} ' . format ( singer , song ) )
s = BeautifulSoup ( r . text )
#Get main lyrics holder
lyrics = s . find ( " div " , { ' class ' : ' lyricbox ' } )
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
comments = lyrics . findAll ( text = lambda text : isinstance ( text , Comment ) )
[ 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
output = str ( lyrics ) . encode ( ' utf-8 ' , errors = ' replace ' ) [ 22 : - 6 : ] . decode ( " utf-8 " ) . replace ( ' \n ' , ' ' ) . replace ( ' <br/> ' , ' \n ' )
try :
return output
except :
return output . encode ( ' utf-8 ' )
class MainWindow ( Gtk . ApplicationWindow ) :
2020-02-25 00:31:39 +03:00
def __init__ ( self , app , client , settings , emitter ) :
2020-01-11 13:25:15 +03:00
Gtk . ApplicationWindow . __init__ ( self , title = ( " mpdevil " ) , application = app )
self . set_icon_name ( " mpdevil " )
self . settings = settings
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-02-25 00:31:39 +03:00
self . emitter = emitter
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
#actions
save_action = Gio . SimpleAction . new ( " save " , None )
save_action . connect ( " activate " , self . on_save )
self . add_action ( save_action )
settings_action = Gio . SimpleAction . new ( " settings " , None )
settings_action . connect ( " activate " , self . on_settings )
self . add_action ( settings_action )
stats_action = Gio . SimpleAction . new ( " stats " , None )
stats_action . connect ( " activate " , self . on_stats )
self . add_action ( stats_action )
update_action = Gio . SimpleAction . new ( " update " , None )
update_action . connect ( " activate " , self . on_update )
self . add_action ( update_action )
#widgets
2020-02-25 00:31:39 +03:00
self . browser = Browser ( self . client , self . settings , self . emitter , self )
self . profiles = ProfileSelect ( self . client , self . settings , self . emitter )
2020-01-11 13:25:15 +03:00
self . profiles . set_tooltip_text ( _ ( " Select profile " ) )
2020-02-25 00:31:39 +03:00
self . control = ClientControl ( self . client , self . settings , self . emitter )
2020-01-11 13:25:15 +03:00
self . progress = SeekBar ( self . client )
2020-01-28 21:59:14 +03:00
self . go_home_button = Gtk . Button ( image = Gtk . Image . new_from_icon_name ( " go-home-symbolic " , self . icon_size ) )
2020-02-28 19:32:15 +03:00
self . go_home_button . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
self . go_home_button . set_tooltip_text ( _ ( " Return to album of current title " ) )
2020-03-01 23:21:06 +03:00
self . search_button = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " system-search-symbolic " , self . icon_size ) )
2020-02-28 19:32:15 +03:00
self . search_button . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
self . search_button . set_tooltip_text ( _ ( " Title search " ) )
2020-01-28 21:59:14 +03:00
self . lyrics_button = Gtk . ToggleButton ( image = Gtk . Image . new_from_icon_name ( " media-view-subtitles-symbolic " , self . icon_size ) )
2020-02-28 19:32:15 +03:00
self . lyrics_button . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
self . lyrics_button . set_tooltip_text ( _ ( " Show lyrics " ) )
2020-02-25 00:31:39 +03:00
self . play_opts = PlaybackOptions ( self . client , self . settings , self . emitter )
2020-01-11 13:25:15 +03:00
#menu
menu = Gio . Menu ( )
menu . append ( _ ( " Save window size " ) , " win.save " )
menu . append ( _ ( " Settings " ) , " win.settings " )
menu . append ( _ ( " Update database " ) , " win.update " )
menu . append ( _ ( " Server stats " ) , " win.stats " )
menu . append ( _ ( " About " ) , " app.about " )
menu . append ( _ ( " Quit " ) , " app.quit " )
menu_button = Gtk . MenuButton . new ( )
2020-02-28 19:32:15 +03:00
menu_button . set_can_focus ( False )
2020-01-11 13:25:15 +03:00
menu_popover = Gtk . Popover . new_from_model ( menu_button , menu )
menu_button . set_popover ( menu_popover )
menu_button . set_tooltip_text ( _ ( " Main menu " ) )
#connect
self . go_home_button . connect ( " clicked " , self . browser . go_home )
2020-03-01 23:21:06 +03:00
self . search_button . connect ( " toggled " , self . on_search_toggled )
2020-01-11 13:25:15 +03:00
self . lyrics_button . connect ( " toggled " , self . on_lyrics_toggled )
2020-02-07 22:13:38 +03:00
self . settings . connect ( " changed::profiles " , self . on_settings_changed )
2020-02-25 00:31:39 +03:00
self . player_changed = self . emitter . connect ( " player " , self . title_update )
self . disconnected_signal = self . emitter . connect ( " disconnected " , self . on_disconnected )
self . reconnected_signal = self . 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-02-02 20:08:59 +03:00
self . vbox = Gtk . Box ( orientation = Gtk . Orientation . VERTICAL , spacing = 4 )
self . hbox = Gtk . Box ( orientation = Gtk . Orientation . HORIZONTAL , spacing = 4 )
2020-02-03 01:55:16 +03:00
self . hbox . set_margin_start ( 2 )
self . hbox . set_margin_end ( 2 )
2020-02-02 20:08:59 +03:00
self . hbox . set_margin_bottom ( 2 )
2020-01-11 13:25:15 +03:00
self . vbox . pack_start ( self . browser , True , True , 0 )
self . vbox . pack_start ( self . hbox , False , False , 0 )
self . hbox . pack_start ( self . control , False , False , 0 )
self . hbox . pack_start ( self . progress , True , True , 10 )
self . hbox . pack_start ( self . go_home_button , False , False , 0 )
self . hbox . pack_start ( self . search_button , False , False , 0 )
self . hbox . pack_start ( self . lyrics_button , False , False , 0 )
2020-02-07 22:13:38 +03:00
if len ( self . settings . get_value ( " profiles " ) ) > 1 :
self . hbox . pack_start ( self . profiles , False , False , 0 )
2020-01-11 13:25:15 +03:00
self . hbox . pack_start ( self . play_opts , False , False , 0 )
self . hbox . pack_end ( menu_button , False , False , 0 )
self . add ( self . vbox )
2020-01-27 00:38:06 +03:00
2020-01-19 02:00:40 +03:00
self . show_all ( )
2020-01-11 13:25:15 +03:00
2020-02-25 00:31:39 +03:00
def title_update ( self , * args ) :
try :
status = self . client . status ( )
if status [ " songid " ] == None :
2020-01-11 13:25:15 +03:00
self . set_title ( " mpdevil " )
2020-02-25 00:31:39 +03:00
else :
song = self . client . playlistid ( status [ " songid " ] ) [ 0 ]
self . set_title ( song [ " artist " ] + " - " + song [ " title " ] + " - " + song [ " album " ] )
if not self . is_active ( ) and self . settings . get_boolean ( " send-notify " ) and status [ " state " ] == " play " :
notify = Gio . Notification . new ( title = song [ " title " ] )
notify . set_body ( song [ " artist " ] + " \n " + song [ " album " ] )
self . app . send_notification ( None , notify )
except :
self . set_title ( " mpdevil " )
def on_reconnected ( self , * args ) :
self . client . try_connect_default ( )
self . progress . set_sensitive ( True )
self . control . set_sensitive ( True )
self . play_opts . set_sensitive ( True )
self . go_home_button . set_sensitive ( True )
self . search_button . set_sensitive ( True )
self . lyrics_button . set_sensitive ( True )
self . emitter . emit ( " playlist " )
self . emitter . emit ( " player " )
self . emitter . emit ( " options " )
self . emitter . emit ( " mixer " )
self . emitter . emit ( " update " )
self . browser . go_home ( )
def on_disconnected ( self , * args ) :
self . lyrics_button . set_active ( False )
2020-03-01 23:21:06 +03:00
self . search_button . set_active ( False )
2020-02-25 00:31:39 +03:00
self . set_title ( " mpdevil (not connected) " )
self . songid_playing = None
self . browser . clear ( )
self . progress . set_sensitive ( False )
self . control . set_sensitive ( False )
self . play_opts . set_sensitive ( False )
self . go_home_button . set_sensitive ( False )
self . search_button . set_sensitive ( False )
self . lyrics_button . set_sensitive ( False )
2020-01-11 13:25:15 +03:00
2020-03-01 23:21:06 +03:00
def on_search_toggled ( self , widget ) :
if widget . get_active ( ) :
if self . client . connected ( ) :
def set_active ( * args ) :
self . search_button . set_active ( False )
self . search_win = SearchWindow ( self . client )
self . search_win . connect ( " destroy " , set_active )
else :
self . search_win . destroy ( )
2020-01-11 13:25:15 +03:00
def on_lyrics_toggled ( self , widget ) :
if widget . get_active ( ) :
if self . client . connected ( ) :
def set_active ( * args ) :
self . lyrics_button . set_active ( False )
2020-02-25 00:31:39 +03:00
self . lyrics_win = LyricsWindow ( self . client , self . settings , self . emitter )
2020-01-11 13:25:15 +03:00
self . lyrics_win . connect ( " destroy " , set_active )
else :
self . lyrics_win . destroy ( )
def on_key_press_event ( self , widget , event ) :
2020-02-05 23:35:19 +03:00
if event . keyval == 32 : #space
self . control . play_button . grab_focus ( )
if event . keyval == 269025044 : #AudioPlay
2020-02-05 22:29:34 +03:00
self . control . play_button . emit ( " clicked " )
2020-02-25 00:31:39 +03:00
elif event . keyval == 269025047 or event . keyval == 43 or event . keyval == 65451 : #AudioNext
2020-02-05 22:29:34 +03:00
self . control . next_button . emit ( " clicked " )
2020-02-25 00:31:39 +03:00
elif event . keyval == 269025046 or event . keyval == 45 or event . keyval == 65453 : #AudioPrev
2020-02-05 22:29:34 +03:00
self . control . prev_button . emit ( " clicked " )
2020-02-29 10:32:07 +03:00
elif event . keyval == 65307 : #esc
self . go_home_button . emit ( " clicked " )
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 ] )
self . browser . save_settings ( )
def on_settings ( self , action , param ) :
settings = SettingsDialog ( self , self . settings )
settings . run ( )
settings . destroy ( )
def on_stats ( self , action , param ) :
if self . client . connected ( ) :
stats = ServerStats ( self , self . client )
stats . run ( )
stats . destroy ( )
def on_update ( self , action , param ) :
if self . client . connected ( ) :
self . client . update ( )
2020-02-07 22:13:38 +03:00
def on_settings_changed ( self , * args ) :
self . hbox . remove ( self . profiles )
if len ( self . settings . get_value ( " profiles " ) ) > 1 :
self . hbox . pack_start ( self . profiles , False , False , 0 )
self . hbox . reorder_child ( self . profiles , 5 )
self . profiles . show ( )
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-01-28 20:39:18 +03:00
self . settings = Settings ( )
2020-01-27 00:23:21 +03:00
self . client = Client ( self . settings )
2020-02-25 00:31:39 +03:00
self . emitter = MpdEventEmitter ( 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-02-25 00:31:39 +03:00
self . window = MainWindow ( self , self . client , self . settings , self . emitter )
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 )
action = Gio . SimpleAction . new ( " about " , None )
action . connect ( " activate " , self . on_about )
self . add_action ( action )
action = Gio . SimpleAction . new ( " quit " , None )
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__ ' :
app = mpdevil ( )
app . run ( sys . argv )