Merge branch “issue-36-server-panel” (close #36, #37, #38, #35)

This commit is contained in:
coderkun 2017-12-26 10:16:54 +01:00
commit 16c2ffd775
8 changed files with 1442 additions and 534 deletions

View file

@ -42,7 +42,7 @@
<description>Window maximized state.</description>
</key>
<key type="i" name="panel">
<range min="1" max="3"/>
<range min="0" max="3"/>
<default>1</default>
<summary>Last selected panel</summary>
<description>The index of the last selected panel.</description>

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -1,15 +1,15 @@
msgid ""
msgstr ""
"Project-Id-Version: CoverGrid (mcg)\n"
"POT-Creation-Date: 2017-09-10 14:16+0200\n"
"PO-Revision-Date: 2017-09-10 14:16+0200\n"
"POT-Creation-Date: 2017-12-25 17:10+0100\n"
"PO-Revision-Date: 2017-12-25 17:10+0100\n"
"Last-Translator: coderkun <olli@coderkun.de>\n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"X-Generator: Poedit 2.0.5\n"
"X-Poedit-Basepath: ../../..\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
@ -58,87 +58,147 @@ msgstr "nach Titel"
msgid "sort by year"
msgstr "nach Jahr"
#: data/gtk.glade:436
#: data/gtk.glade:437
msgid "Enter hostname or IP address"
msgstr "Hostnamen oder IP-Adresse eingeben"
#: data/gtk.glade:448
#: data/gtk.glade:449
msgid "Enter URL or local path"
msgstr "URL oder lokalen Pfad eingeben"
#: data/gtk.glade:460
#: data/gtk.glade:461
msgid "Enter password or leave blank"
msgstr "Passwort eingeben oder leer lassen"
#: data/gtk.glade:485
#: data/gtk.glade:487
msgid "Host:"
msgstr "Host:"
#: data/gtk.glade:497
#: data/gtk.glade:499
msgid "Port:"
msgstr "Port:"
#: data/gtk.glade:509
#: data/gtk.glade:511
msgid "Password:"
msgstr "Passwort:"
#: data/gtk.glade:521
#: data/gtk.glade:523
msgid "Image Directory:"
msgstr "Bildordner:"
#: data/gtk.glade:545 data/gtk.menu.ui:24
msgid "Connection"
msgstr "Verbindung"
#: data/gtk.glade:619
msgid "File:"
msgstr "Datei:"
#: data/gtk.glade:714 data/gtk.menu.ui:30
#: data/gtk.glade:631
msgid "Audio:"
msgstr "Audio:"
#: data/gtk.glade:643
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/gtk.glade:655
msgid "Error:"
msgstr "Fehler:"
#: data/gtk.glade:667 data/gtk.glade:681 data/gtk.glade:695 data/gtk.glade:709
msgid "<i>none</i>"
msgstr "<i>nichts</i>"
#: data/gtk.glade:742
msgid "Status"
msgstr "Status"
#: data/gtk.glade:827
msgid "Albums"
msgstr "Alben"
#: data/gtk.glade:839
msgid "Songs"
msgstr "Songs"
#: data/gtk.glade:851
msgid "Artists"
msgstr "Künstler"
#: data/gtk.glade:875
msgid "Seconds"
msgstr "Sekunden"
#: data/gtk.glade:922
msgid "Seconds played"
msgstr "Sekunden gespielt"
#: data/gtk.glade:933
msgid "Seconds running"
msgstr "Sekunden laufend"
#: data/gtk.glade:964
msgid "Statistics"
msgstr "Statistiken"
#: data/gtk.glade:1034
msgid "Audio Devices"
msgstr "Audiogeräte"
#: data/gtk.glade:1048
msgid "Server"
msgstr "Server"
#: data/gtk.glade:1217 data/gtk.menu.ui:30
msgid "Cover"
msgstr "Cover"
#: data/gtk.glade:856 data/gtk.menu.ui:36
#: data/gtk.glade:1359 data/gtk.menu.ui:36
msgid "Playlist"
msgstr "Wiedergabeliste"
#: data/gtk.glade:883
#: data/gtk.glade:1386
msgid "search library"
msgstr "Bibliothek durchsuchen"
#: data/gtk.glade:1042 data/gtk.menu.ui:42
#: data/gtk.glade:1545 data/gtk.menu.ui:42
msgid "Library"
msgstr "Bibliothek"
#: data/gtk.glade:1114 data/gtk.shortcuts.ui:74
#: data/gtk.glade:1624 data/gtk.shortcuts.ui:74
msgid "Connect or disconnect"
msgstr "Die Verbindung herstellen oder trennen"
#: data/gtk.glade:1136 data/gtk.shortcuts.ui:81
#: data/gtk.glade:1646 data/gtk.shortcuts.ui:81
msgid "Switch between play and pause"
msgstr "Zwischen Abspielen und Pause wechseln"
#: data/gtk.glade:1156
#: data/gtk.glade:1666
msgid "Adjust the volume"
msgstr "Die Lautstärke anpassen"
#: data/gtk.glade:1214 data/gtk.shortcuts.ui:101
#: data/gtk.glade:1710
msgid "Connect to MPD"
msgstr "Zu MPD verbinden"
#: data/gtk.glade:1752 data/gtk.shortcuts.ui:101
msgid "Show the cover in fullscreen mode"
msgstr "Das Cover im Vollbildmodus anzeigen"
#: data/gtk.glade:1237 data/gtk.glade:1386
#: data/gtk.glade:1775 data/gtk.glade:1924
msgid "Settings and actions"
msgstr "Einstellungen und Aktionen"
#: data/gtk.glade:1285 data/gtk.glade:1364
#: data/gtk.glade:1823 data/gtk.glade:1902
msgid "Select multiple albums"
msgstr "Mehrere Alben auswählen"
#: data/gtk.glade:1307 data/gtk.shortcuts.ui:88
#: data/gtk.glade:1845 data/gtk.shortcuts.ui:88
msgid "Clear the playlist"
msgstr "Die Wiedergabeliste leeren"
#: data/gtk.glade:1341 data/gtk.shortcuts.ui:114
#: data/gtk.glade:1879 data/gtk.shortcuts.ui:114
msgid "Search the library"
msgstr "Die Bibliothek durchsuchen"
#: data/gtk.glade:1431
#: data/gtk.glade:1969
msgid ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
@ -158,6 +218,10 @@ msgstr "Abspielen"
msgid "Clear Playlist"
msgstr "Playlist leeren"
#: data/gtk.menu.ui:24
msgid "Connection"
msgstr "Verbindung"
#: data/gtk.menu.ui:50
msgid "Keyboard Shortcuts"
msgstr "Tastenkombinationen"
@ -229,6 +293,3 @@ msgstr "{} mit {}"
#~ msgid "Show the keyboard shortcuts (this dialog)"
#~ msgstr "Die Tastenkombinationen anzeigen (dieser Dialog)"
#~ msgid "Server"
#~ msgstr "Server"

Binary file not shown.

View file

@ -1,15 +1,15 @@
msgid ""
msgstr ""
"Project-Id-Version: CoverGrid (mcg)\n"
"POT-Creation-Date: 2017-09-10 14:15+0200\n"
"PO-Revision-Date: 2017-09-10 14:16+0200\n"
"POT-Creation-Date: 2017-12-25 17:08+0100\n"
"PO-Revision-Date: 2017-12-25 17:08+0100\n"
"Last-Translator: coderkun <olli@coderkun.de>\n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"X-Generator: Poedit 2.0.5\n"
"X-Poedit-Basepath: ../../..\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SearchPath-0: data/gtk.glade\n"
@ -57,87 +57,147 @@ msgstr "by Title"
msgid "sort by year"
msgstr "by Year"
#: data/gtk.glade:436
#: data/gtk.glade:437
msgid "Enter hostname or IP address"
msgstr "Enter hostname or IP address"
#: data/gtk.glade:448
#: data/gtk.glade:449
msgid "Enter URL or local path"
msgstr "Enter URL or local path"
#: data/gtk.glade:460
#: data/gtk.glade:461
msgid "Enter password or leave blank"
msgstr "Enter password or leave blank"
#: data/gtk.glade:485
#: data/gtk.glade:487
msgid "Host:"
msgstr "Host:"
#: data/gtk.glade:497
#: data/gtk.glade:499
msgid "Port:"
msgstr "Port:"
#: data/gtk.glade:509
#: data/gtk.glade:511
msgid "Password:"
msgstr "Password:"
#: data/gtk.glade:521
#: data/gtk.glade:523
msgid "Image Directory:"
msgstr "Image Directory:"
#: data/gtk.glade:545 data/gtk.menu.ui:24
msgid "Connection"
msgstr "Connection"
#: data/gtk.glade:619
msgid "File:"
msgstr "File:"
#: data/gtk.glade:714 data/gtk.menu.ui:30
#: data/gtk.glade:631
msgid "Audio:"
msgstr "Audio:"
#: data/gtk.glade:643
msgid "Bitrate:"
msgstr "Bitrate:"
#: data/gtk.glade:655
msgid "Error:"
msgstr "Error:"
#: data/gtk.glade:667 data/gtk.glade:681 data/gtk.glade:695 data/gtk.glade:709
msgid "<i>none</i>"
msgstr "<i>none</i>"
#: data/gtk.glade:742
msgid "Status"
msgstr "Status"
#: data/gtk.glade:827
msgid "Albums"
msgstr "Albums"
#: data/gtk.glade:839
msgid "Songs"
msgstr "Songs"
#: data/gtk.glade:851
msgid "Artists"
msgstr "Artists"
#: data/gtk.glade:875
msgid "Seconds"
msgstr "Seconds"
#: data/gtk.glade:922
msgid "Seconds played"
msgstr "Seconds"
#: data/gtk.glade:933
msgid "Seconds running"
msgstr "Seconds running"
#: data/gtk.glade:964
msgid "Statistics"
msgstr "Statistics"
#: data/gtk.glade:1034
msgid "Audio Devices"
msgstr "Audio Devices"
#: data/gtk.glade:1048
msgid "Server"
msgstr "Server"
#: data/gtk.glade:1217 data/gtk.menu.ui:30
msgid "Cover"
msgstr "Cover"
#: data/gtk.glade:856 data/gtk.menu.ui:36
#: data/gtk.glade:1359 data/gtk.menu.ui:36
msgid "Playlist"
msgstr "Playlist"
#: data/gtk.glade:883
#: data/gtk.glade:1386
msgid "search library"
msgstr "search library"
#: data/gtk.glade:1042 data/gtk.menu.ui:42
#: data/gtk.glade:1545 data/gtk.menu.ui:42
msgid "Library"
msgstr "Library"
#: data/gtk.glade:1114 data/gtk.shortcuts.ui:74
#: data/gtk.glade:1624 data/gtk.shortcuts.ui:74
msgid "Connect or disconnect"
msgstr "Connect or disconnect"
#: data/gtk.glade:1136 data/gtk.shortcuts.ui:81
#: data/gtk.glade:1646 data/gtk.shortcuts.ui:81
msgid "Switch between play and pause"
msgstr "Switch between play and pause"
#: data/gtk.glade:1156
#: data/gtk.glade:1666
msgid "Adjust the volume"
msgstr "Adjust the volume"
#: data/gtk.glade:1214 data/gtk.shortcuts.ui:101
#: data/gtk.glade:1710
msgid "Connect to MPD"
msgstr "Connect to MPD"
#: data/gtk.glade:1752 data/gtk.shortcuts.ui:101
msgid "Show the cover in fullscreen mode"
msgstr "Show the cover in fullscreen mode"
#: data/gtk.glade:1237 data/gtk.glade:1386
#: data/gtk.glade:1775 data/gtk.glade:1924
msgid "Settings and actions"
msgstr "Settings and actions"
#: data/gtk.glade:1285 data/gtk.glade:1364
#: data/gtk.glade:1823 data/gtk.glade:1902
msgid "Select multiple albums"
msgstr "Select multiple albums"
#: data/gtk.glade:1307 data/gtk.shortcuts.ui:88
#: data/gtk.glade:1845 data/gtk.shortcuts.ui:88
msgid "Clear the playlist"
msgstr "Clear the playlist"
#: data/gtk.glade:1341 data/gtk.shortcuts.ui:114
#: data/gtk.glade:1879 data/gtk.shortcuts.ui:114
msgid "Search the library"
msgstr "Search the library"
#: data/gtk.glade:1431
#: data/gtk.glade:1969
msgid ""
"CoverGrid is a client for the Music Player Daemon, focusing on albums "
"instead of single tracks."
@ -157,6 +217,10 @@ msgstr "Play"
msgid "Clear Playlist"
msgstr "Clear Playlist"
#: data/gtk.menu.ui:24
msgid "Connection"
msgstr "Connection"
#: data/gtk.menu.ui:50
msgid "Keyboard Shortcuts"
msgstr "Keyboard Shortcuts"
@ -228,6 +292,3 @@ msgstr "{} feat. {}"
#~ msgid "_Quit"
#~ msgstr "_Quit"
#~ msgid "Server"
#~ msgstr "Server"

View file

@ -108,10 +108,14 @@ class Client(Base):
SIGNAL_CONNECTION = 'connection'
# Signal: status
SIGNAL_STATUS = 'status'
# Signal: stats
SIGNAL_STATS = 'stats'
# Signal: load albums
SIGNAL_LOAD_ALBUMS = 'load-albums'
# Signal: load playlist
SIGNAL_LOAD_PLAYLIST = 'load-playlist'
# Signal: load audio output devices
SIGNAL_LOAD_OUTPUT_DEVICES = 'load-output-devices'
# Signal: error
SIGNAL_ERROR = 'error'
@ -174,6 +178,25 @@ class Client(Base):
self._add_action(self._get_status)
def get_stats(self):
"""Load statistics."""
self._logger.info("get stats")
self._add_action(self._get_stats)
def get_output_devices(self):
"""Determine the list of audio output devices."""
self._logger.info("get output devices")
self._add_action(self._get_output_devices)
def enable_output_device(self, device, enabled):
"""Enable/disable an audio output device."""
self._logger.info("enable output device")
self._add_action(self._enable_output_device, device, enabled)
def load_albums(self):
self._logger.info("load albums")
self._add_action(self._load_albums)
@ -343,6 +366,9 @@ class Client(Base):
self.load_albums()
self.load_playlist()
self.get_status()
if subsystems['changed'] == 'output':
self.get_output_devices()
self.get_status()
def _noidle(self):
@ -375,10 +401,14 @@ class Client(Base):
if 'error' in status:
error = status['error']
# Album
file = None
album = None
pos = 0
song = self._parse_dict(self._call("currentsong"))
if song:
# File
if 'file' in song:
file = song['file']
# Track
track = self._extract_playlist_track(song)
if track:
@ -391,7 +421,66 @@ class Client(Base):
album = palbum
break
pos = pos - len(palbum.get_tracks())
self._callback(Client.SIGNAL_STATUS, state, album, pos, time, volume, error)
# Audio
audio = None
if 'audio' in status:
audio = status['audio']
# Bitrate
bitrate = None
if 'bitrate' in status:
bitrate = status['bitrate']
self._callback(Client.SIGNAL_STATUS, state, album, pos, time, volume, file, audio, bitrate, error)
def _get_stats(self):
"""Action: Perform the real statistics gathering."""
self._logger.info("getting statistics")
stats = self._parse_dict(self._call("stats"))
self._logger.debug("stats: %r", stats)
# Artists
artists = 0
if 'artists' in stats:
artists = int(stats['artists'])
# Albums
albums = 0
if 'albums' in stats:
albums = int(stats['albums'])
# Songs
songs = 0
if 'songs' in stats:
songs = int(stats['songs'])
# Database playtime
dbplaytime = 0
if 'db_playtime' in stats:
dbplaytime = stats['db_playtime']
# Playtime
playtime = 0
if 'playtime' in stats:
playtime = stats['playtime']
# Uptime
uptime = 0
if 'uptime' in stats:
uptime = stats['uptime']
self._callback(Client.SIGNAL_STATS, artists, albums, songs, dbplaytime, playtime, uptime)
def _get_output_devices(self):
"""Action: Perform the real loading of output devices."""
devices = []
for output in self._parse_list(self._call('outputs'), ['outputid']):
device = OutputDevice(output['outputid'], output['outputname'])
device.set_enabled(int(output['outputenabled']) == 1)
devices.append(device)
self._callback(Client.SIGNAL_LOAD_OUTPUT_DEVICES, devices)
def _enable_output_device(self, device, enabled):
"""Action: Perform the real enabling/disabling of an output device."""
if enabled:
self._call('enableoutput ', device.get_id())
else:
self._call('disableoutput ', device.get_id())
def _load_albums(self):
@ -684,6 +773,33 @@ class Client(Base):
class OutputDevice:
def __init__(self, id, name):
self._id = id
self._name = name
self._enabled = None
def get_id(self):
return self._id
def get_name(self):
return self._name
def set_enabled(self, enabled):
self._enabled = enabled
def is_enabled(self):
return self._enabled
class MCGAlbum:
DEFAULT_ALBUM = 'Various'
_FILE_NAMES = ['cover', 'folder']

View file

@ -88,7 +88,7 @@ class Window():
SETTING_SORT_ORDER = 'sort-order'
SETTING_SORT_TYPE = 'sort-type'
STOCK_ICON_DEFAULT = 'image-x-generic-symbolic'
_PANEL_INDEX_CONNECTION = 0
_PANEL_INDEX_SERVER = 0
_PANEL_INDEX_COVER = 1
_PANEL_INDEX_PLAYLIST = 2
_PANEL_INDEX_LIBRARY = 3
@ -107,8 +107,10 @@ class Window():
self._maximized = self._settings.get_boolean(Window.SETTING_WINDOW_MAXIMIZED)
self._fullscreened = False
# Login screen
self._connection_panel = ConnectionPanel(builder)
# Panels
self._panels.append(ConnectionPanel(builder))
self._panels.append(ServerPanel(builder))
self._panels.append(CoverPanel(builder))
self._panels.append(PlaylistPanel(builder))
self._panels.append(LibraryPanel(builder))
@ -117,6 +119,7 @@ class Window():
# InfoBar
self._infobar = InfoBar(builder)
# Stack
self._content_stack = builder.get_object('contentstack')
self._stack = builder.get_object('panelstack')
# Header
self._header_bar = HeaderBar(builder)
@ -125,11 +128,11 @@ class Window():
# Properties
self._header_bar.set_sensitive(False, False)
self._panels[Window._PANEL_INDEX_CONNECTION].set_host(self._settings.get_string(Window.SETTING_HOST))
self._panels[Window._PANEL_INDEX_CONNECTION].set_port(self._settings.get_int(Window.SETTING_PORT))
self._connection_panel.set_host(self._settings.get_string(Window.SETTING_HOST))
self._connection_panel.set_port(self._settings.get_int(Window.SETTING_PORT))
if use_keyring:
self._panels[Window._PANEL_INDEX_CONNECTION].set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME))
self._panels[Window._PANEL_INDEX_CONNECTION].set_image_dir(self._settings.get_string(Window.SETTING_IMAGE_DIR))
self._connection_panel.set_password(keyring.get_password(ZeroconfProvider.KEYRING_SYSTEM, ZeroconfProvider.KEYRING_USERNAME))
self._connection_panel.set_image_dir(self._settings.get_string(Window.SETTING_IMAGE_DIR))
self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(self._settings.get_enum(Window.SETTING_TRACKLIST_SIZE))
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
self._panels[Window._PANEL_INDEX_LIBRARY].set_item_size(self._settings.get_int(Window.SETTING_ITEM_SIZE))
@ -141,7 +144,8 @@ class Window():
self._header_bar.connect('toolbar-connect', self.on_header_bar_connect)
self._header_bar.connect('toolbar-playpause', self.on_header_bar_playpause)
self._header_bar.connect('toolbar-set-volume', self.on_header_bar_set_volume)
self._panels[Window._PANEL_INDEX_CONNECTION].connect('connection-changed', self.on_connection_panel_connection_changed)
self._connection_panel.connect('connection-changed', self.on_connection_panel_connection_changed)
self._panels[Window._PANEL_INDEX_SERVER].connect('change-output-device', self.on_server_panel_output_device_changed)
self._panels[Window._PANEL_INDEX_COVER].connect('toggle-fullscreen', self.on_cover_panel_toggle_fullscreen)
self._panels[Window._PANEL_INDEX_COVER].connect('tracklist-size-changed', self.on_cover_panel_tracklist_size_changed)
self._panels[Window._PANEL_INDEX_COVER].connect('set-song', self.on_cover_panel_set_song)
@ -157,6 +161,8 @@ class Window():
self._panels[Window._PANEL_INDEX_LIBRARY].connect('sort-type-changed', self.on_library_panel_sort_type_changed)
self._mcg.connect_signal(client.Client.SIGNAL_CONNECTION, self.on_mcg_connect)
self._mcg.connect_signal(client.Client.SIGNAL_STATUS, self.on_mcg_status)
self._mcg.connect_signal(client.Client.SIGNAL_STATS, self.on_mcg_stats)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_OUTPUT_DEVICES, self.on_mcg_load_output_devices)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
self._mcg.connect_signal(client.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
self._mcg.connect_signal(client.Client.SIGNAL_ERROR, self.on_mcg_error)
@ -172,7 +178,7 @@ class Window():
}
handlers.update(self._header_bar.get_signal_handlers())
handlers.update(self._infobar.get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_CONNECTION].get_signal_handlers())
handlers.update(self._connection_panel.get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_COVER].get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_PLAYLIST].get_signal_handlers())
handlers.update(self._panels[Window._PANEL_INDEX_LIBRARY].get_signal_handlers())
@ -183,7 +189,7 @@ class Window():
if self._maximized:
self._appwindow.maximize()
self._appwindow.show_all()
self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get())
self._content_stack.set_visible_child(self._connection_panel.get())
if self._settings.get_boolean(Window.SETTING_CONNECTED):
self._connect()
@ -298,6 +304,10 @@ class Window():
self._mcg.play_album_from_playlist(album)
def on_server_panel_output_device_changed(self, widget, device, enabled):
self._mcg.enable_output_device(device, enabled)
def on_cover_panel_toggle_fullscreen(self, widget):
if not self._fullscreened:
self._appwindow.fullscreen()
@ -346,6 +356,8 @@ class Window():
self._mcg.load_playlist()
self._mcg.load_albums()
self._mcg.get_status()
self._mcg.get_stats()
self._mcg.get_output_devices()
self._connect_action.set_state(GLib.Variant.new_boolean(True))
self._play_action.set_enabled(True)
self._clear_playlist_action.set_enabled(True)
@ -358,7 +370,7 @@ class Window():
self._panel_action.set_enabled(False)
def on_mcg_status(self, state, album, pos, time, volume, error):
def on_mcg_status(self, state, album, pos, time, volume, file, audio, bitrate, error):
# Album
GObject.idle_add(self._panels[Window._PANEL_INDEX_COVER].set_album, album)
if not album and self._fullscreened:
@ -374,6 +386,8 @@ class Window():
self._play_action.set_state(GLib.Variant.new_boolean(False))
# Volume
GObject.idle_add(self._header_bar.set_volume, volume)
# Status
self._panels[Window._PANEL_INDEX_SERVER].set_status(file, audio, bitrate, error)
# Error
if error is None:
self._infobar.hide()
@ -381,12 +395,20 @@ class Window():
self._show_error(error)
def on_mcg_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self._panels[Window._PANEL_INDEX_SERVER].set_stats(artists, albums, songs, dbplaytime, playtime, uptime)
def on_mcg_load_output_devices(self, devices):
self._panels[Window._PANEL_INDEX_SERVER].set_output_devices(devices)
def on_mcg_load_playlist(self, playlist):
self._panels[self._PANEL_INDEX_PLAYLIST].set_playlist(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), playlist)
self._panels[self._PANEL_INDEX_PLAYLIST].set_playlist(self._connection_panel.get_host(), playlist)
def on_mcg_load_albums(self, albums):
self._panels[self._PANEL_INDEX_LIBRARY].set_albums(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), albums)
self._panels[self._PANEL_INDEX_LIBRARY].set_albums(self._connection_panel.get_host(), albums)
def on_mcg_error(self, error):
@ -424,17 +446,16 @@ class Window():
# Private methods
def _connect(self):
connection_panel = self._panels[Window._PANEL_INDEX_CONNECTION]
connection_panel.get().set_sensitive(False)
self._connection_panel.get().set_sensitive(False)
self._header_bar.set_sensitive(False, True)
if self._mcg.is_connected():
self._mcg.disconnect()
self._settings.set_boolean(Window.SETTING_CONNECTED, False)
else:
host = connection_panel.get_host()
port = connection_panel.get_port()
password = connection_panel.get_password()
image_dir = connection_panel.get_image_dir()
host = self._connection_panel.get_host()
port = self._connection_panel.get_port()
password = self._connection_panel.get_password()
image_dir = self._connection_panel.get_image_dir()
self._mcg.connect(host, port, password, image_dir)
self._settings.set_boolean(Window.SETTING_CONNECTED, True)
@ -442,6 +463,7 @@ class Window():
def _connect_connected(self):
self._header_bar.connected()
self._header_bar.set_sensitive(True, False)
self._content_stack.set_visible_child(self._stack)
self._stack.set_visible_child(self._panels[self._settings.get_int(Window.SETTING_PANEL)].get())
@ -451,8 +473,8 @@ class Window():
self._header_bar.disconnected()
self._header_bar.set_sensitive(False, False)
self._save_visible_panel()
self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get())
self._panels[Window._PANEL_INDEX_CONNECTION].get().set_sensitive(True)
self._content_stack.set_visible_child(self._connection_panel.get())
self._connection_panel.get().set_sensitive(True)
def _fullscreen(self, fullscreened_new):
@ -469,8 +491,7 @@ class Window():
def _save_visible_panel(self):
panels = [panel.get() for panel in self._panels]
panel_index_selected = panels.index(self._stack.get_visible_child())
if panel_index_selected > 0:
self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
self._settings.set_int(Window.SETTING_PANEL, panel_index_selected)
def _set_menu_visible_panel(self):
@ -508,6 +529,8 @@ class HeaderBar(GObject.GObject):
# Widgets
self._header_bar = builder.get_object('headerbar')
self._title_stack = builder.get_object('headerbar-title-stack')
self._connection_label = builder.get_object('headerbar-connectionn-label')
self._stack_switcher = StackSwitcher(builder)
self._button_connect = builder.get_object('headerbar-connection')
self._button_playpause = builder.get_object('headerbar-playpause')
@ -582,6 +605,7 @@ class HeaderBar(GObject.GObject):
self._button_connect.handler_unblock_by_func(
self.on_connection_active_notify
)
self._title_stack.set_visible_child(self._stack_switcher.get())
def disconnected(self):
@ -593,6 +617,7 @@ class HeaderBar(GObject.GObject):
self._button_connect.handler_unblock_by_func(
self.on_connection_active_notify
)
self._title_stack.set_visible_child(self._connection_label)
def set_play(self):
@ -675,8 +700,7 @@ class ConnectionPanel(GObject.GObject):
self._profile = None
# Widgets
self._panel = builder.get_object('server-panel')
self._toolbar = builder.get_object('server-toolbar')
self._panel = builder.get_object('connection-panel')
# Zeroconf
self._zeroconf_list = builder.get_object('server-zeroconf-list')
self._zeroconf_list.set_model(self._services)
@ -701,10 +725,6 @@ class ConnectionPanel(GObject.GObject):
return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self):
return {
'on_server-zeroconf-list-selection_changed': self.on_service_selected,
@ -792,6 +812,118 @@ class ConnectionPanel(GObject.GObject):
class ServerPanel(GObject.GObject):
__gsignals__ = {
'change-output-device': (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT,bool,)),
}
def __init__(self, builder):
GObject.GObject.__init__(self)
self._none_label = ""
self._output_buttons = {}
# Widgets
self._panel = builder.get_object('server-panel')
self._toolbar = builder.get_object('server-toolbar')
self._stack = builder.get_object('server-stack')
# Status widgets
self._status_file = builder.get_object('server-status-file')
self._status_audio = builder.get_object('server-status-audio')
self._status_bitrate = builder.get_object('server-status-bitrate')
self._status_error = builder.get_object('server-status-error')
self._none_label = self._status_file.get_label()
# Stats widgets
self._stats_artists = builder.get_object('server-stats-artists')
self._stats_albums = builder.get_object('server-stats-albums')
self._stats_songs = builder.get_object('server-stats-songs')
self._stats_dbplaytime = builder.get_object('server-stats-dbplaytime')
self._stats_playtime = builder.get_object('server-stats-playtime')
self._stats_uptime = builder.get_object('server-stats-uptime')
# Audio ouptut devices widgets
self._output_devices = builder.get_object('server-output-devices')
def get(self):
return self._panel
def get_toolbar(self):
return self._toolbar
def on_output_device_toggled(self, widget, device):
self.emit('change-output-device', device, widget.get_active())
def set_status(self, file, audio, bitrate, error):
if file:
file = GObject.markup_escape_text(file)
else:
file = self._none_label
self._status_file.set_markup(file)
# Audio information
if audio:
parts = audio.split(":")
if len(parts) == 3:
audio = "{}Hz, {}bit, {}channels".format(parts[0], parts[1], parts[2])
else:
audio = self._none_label
self._status_audio.set_markup(audio)
# Bitrate
if bitrate:
bitrate = bitrate + "kb/s"
else:
bitrate = self._none_label
self._status_bitrate.set_markup(bitrate)
# Error
if error:
error = GObject.markup_escape_text(error)
else:
error = self._none_label
self._status_error.set_markup(error)
def set_stats(self, artists, albums, songs, dbplaytime, playtime, uptime):
self._stats_artists.set_text(str(artists))
self._stats_albums.set_text(str(albums))
self._stats_songs.set_text(str(songs))
self._stats_dbplaytime.set_text(str(dbplaytime))
self._stats_playtime.set_text(str(playtime))
self._stats_uptime.set_text(str(uptime))
def set_output_devices(self, devices):
device_ids = []
# Add devices
for device in devices:
device_ids.append(device.get_id())
if device.get_id() in self._output_buttons.keys():
self._output_buttons[device.get_id()].freeze_notify()
self._output_buttons[device.get_id()].set_active(device.is_enabled())
self._output_buttons[device.get_id()].thaw_notify()
else:
button = Gtk.CheckButton(device.get_name())
if device.is_enabled():
button.set_active(True)
handler = button.connect('toggled', self.on_output_device_toggled, device)
self._output_devices.insert(button, -1)
self._output_buttons[device.get_id()] = button
self._output_devices.show_all()
# Remove devices
for id in self._output_buttons.keys():
if id not in device_ids:
self._output_devices.remove(self._output_buttons[id].get_parent())
class CoverPanel(GObject.GObject):
__gsignals__ = {
'toggle-fullscreen': (GObject.SIGNAL_RUN_FIRST, None, ()),