diff --git a/data/de.coderkun.mcg.gschema.xml b/data/de.coderkun.mcg.gschema.xml index 89e599a..57e9896 100644 --- a/data/de.coderkun.mcg.gschema.xml +++ b/data/de.coderkun.mcg.gschema.xml @@ -42,7 +42,7 @@ Window maximized state. - + 1 Last selected panel The index of the last selected panel. diff --git a/data/gtk.glade b/data/gtk.glade index 0e8d916..9192112 100644 --- a/data/gtk.glade +++ b/data/gtk.glade @@ -369,11 +369,13 @@ True False - + True False + 100 + crossfade - + True False vertical @@ -399,7 +401,7 @@ True - + @@ -472,6 +474,7 @@ True True + 6600 server-port-adjustment 6600 @@ -544,509 +547,1016 @@ - connection - Connection + page1 + page1 - + True False - True - + True False - + True False + server-stack - cover-spinner + False + True + 0 - + True - True - False - False - + False - + True False - - True - False - - - - True - False - 0 - - - + - - - - - cover-scroll - 1 - - - - - True - True - 0 - - - - - True - False - slide-right - - - True - True - never - never - - - True - False - none - - + + True False vertical - + + + + True False - 5 - 5 - 5 - True + vertical + 10 - + True False - Album - True + dialog-information-symbolic + 6 - 0 - 0 + False + True + 0 - + True False - Date - True + 2 + 5 + + + True + False + start + File: + + + 0 + 0 + + + + + True + False + start + Audio: + + + 0 + 1 + + + + + True + False + start + Bitrate: + + + 0 + 2 + + + + + True + False + start + Error: + + + 0 + 3 + + + + + True + False + start + <i>none</i> + True + True + + + 1 + 2 + + + + + True + False + start + <i>none</i> + True + True + + + 1 + 1 + + + + + True + False + start + <i>none</i> + True + True + + + 1 + 0 + + + + + True + False + start + <i>none</i> + True + True + + + 1 + 3 + + - 0 - 1 + False + True + 1 - - - True - False - Artist - True - - - 0 - 2 - - - False True - 10 - 0 - - - - - True - True - start - vertical - False - - - - - True - True 1 + + False + True + 1 + + + + + page0 + Status + + + + + True + False + + + + + + True + False + vertical + + + + + + True + False + vertical + 10 + + + True + False + starred-symbolic + 6 + + + False + True + 0 + + + + + True + False + 2 + 5 + + + True + False + end + right + True + + + 0 + 0 + + + + + True + False + end + right + + + 0 + 1 + + + + + True + False + end + right + + + 0 + 2 + + + + + True + False + start + Albums + + + 1 + 1 + + + + + True + False + start + Songs + + + 1 + 2 + + + + + True + False + start + Artists + + + 1 + 0 + + + + + True + False + end + right + + + 0 + 3 + + + + + True + False + start + Seconds + + + 1 + 3 + + + + + True + False + end + right + + + 0 + 6 + + + + + True + False + end + right + + + 0 + 5 + + + + + True + False + + + 0 + 4 + 2 + + + + + True + False + start + Seconds played + + + 1 + 5 + + + + + True + False + Seconds running + + + 1 + 6 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + page1 + Statistics + 1 + + + + + True + False + + + + + + True + False + vertical + + + + + + True + False + vertical + 10 + + + True + False + audio-speakers-symbolic + 6 + + + False + True + 0 + + + + + True + False + none + + + + False + True + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + page2 + Audio Devices + 2 + + + + + True + True + 1 + + + + + server + Server + + + + + True + False + True + + + True + False + + + True + False + + + cover-spinner + + + + + True + True + False + False + + + + True + False + + + True + False + + + + True + False + 0 + + + + + + + + + cover-scroll + 1 + + + + + True + True + 0 + + + + + True + False + slide-right + + + True + True + never + never + + + True + False + none + + + True + False + vertical + + + True + False + 5 + 5 + 5 + True + + + True + False + Album + True + + + 0 + 0 + + + + + True + False + Date + True + + + 0 + 1 + + + + + True + False + Artist + True + + + 0 + 2 + + + + + + False + True + 10 + 0 + + + + + True + True + start + vertical + False + + + + + True + True + 1 + + + + + + + False + True + 1 + - False - True + cover + Cover 1 + + + True + False + slide-left-right + + + True + False + vertical + + + True + True + + + True + True + 0 + horizontal + 0 + 0 + 1 + 0 + True + + + + + + + + + True + True + 0 + + + + + True + False + slide-up + + + True + False + + + + + False + True + 1 + + + + + page2 + page2 + + + + + True + False + vertical + + + True + False + + + True + False + + + standalone-spinne + + + + + True + True + False + False + + + True + False + + + True + False + gtk-missing-image + 6 + + + + + + + standalone-scroll + 1 + + + + + True + True + 0 + + + + + True + False + + + False + True + 1 + + + + + page1 + page1 + 1 + + + + + playlist + Playlist + 2 + + + + + True + False + slide-left-right + + + True + False + vertical + + + True + True + False + + + + True + True + edit-find-symbolic + False + False + search library + + + + + + False + True + 0 + + + + + True + False + none + + + True + False + 0.5 + + + + + False + True + 1 + + + + + True + True + + + True + True + 0 + horizontal + 0 + 0 + 1 + 0 + True + + + + + + + + + True + True + 2 + + + + + True + False + none + + + True + False + + + + + False + True + 3 + + + + + page0 + page0 + + + + + True + False + vertical + + + True + False + + + True + False + + + standalone-spinne + + + + + True + True + False + False + + + + True + False + + + True + False + gtk-missing-image + 6 + + + + + + + standalone-scroll + 1 + + + + + True + True + 0 + + + + + True + False + + + False + True + 1 + + + + + page1 + page1 + 1 + + + + + library + Library + 3 + + - cover - Cover + page0 + page0 1 - - - True - False - slide-left-right - - - True - False - vertical - - - True - True - - - True - True - 0 - horizontal - 0 - 0 - 1 - 0 - True - - - - - - - - - True - True - 0 - - - - - True - False - slide-up - - - True - False - - - - - False - True - 1 - - - - - page2 - page2 - - - - - True - False - vertical - - - True - False - - - True - False - - - standalone-spinne - - - - - True - True - False - False - - - True - False - - - True - False - gtk-missing-image - 6 - - - - - - - standalone-scroll - 1 - - - - - True - True - 0 - - - - - True - False - - - False - True - 1 - - - - - page1 - page1 - 1 - - - - - playlist - Playlist - 2 - - - - - True - False - slide-left-right - - - True - False - vertical - - - True - True - False - - - - True - True - edit-find-symbolic - False - False - search library - - - - - - False - True - 0 - - - - - True - False - none - - - True - False - 0.5 - - - - - False - True - 1 - - - - - True - True - - - True - True - 0 - horizontal - 0 - 0 - 1 - 0 - True - - - - - - - - - - True - True - 2 - - - - - True - False - none - - - True - False - - - - - False - True - 3 - - - - - page0 - page0 - - - - - True - False - vertical - - - True - False - - - True - False - - - standalone-spinne - - - - - True - True - False - False - - - - True - False - - - True - False - gtk-missing-image - 6 - - - - - - - standalone-scroll - 1 - - - - - True - True - 0 - - - - - True - False - - - False - True - 1 - - - - - page1 - page1 - 1 - - - - - library - Library - 3 - - -1 @@ -1190,15 +1700,43 @@ 4 - - - - + + headerbar-connection True False - panelstack + 100 + crossfade + + + True + False + Connect to MPD + + + + + + page1 + page1 + + + + + True + False + panelstack + + + page0 + page0 + 1 + + + + 4 + diff --git a/locale/de/LC_MESSAGES/mcg.mo b/locale/de/LC_MESSAGES/mcg.mo index f9e520a..c32a515 100644 Binary files a/locale/de/LC_MESSAGES/mcg.mo and b/locale/de/LC_MESSAGES/mcg.mo differ diff --git a/locale/de/LC_MESSAGES/mcg.po b/locale/de/LC_MESSAGES/mcg.po index e7e3011..597cb16 100644 --- a/locale/de/LC_MESSAGES/mcg.po +++ b/locale/de/LC_MESSAGES/mcg.po @@ -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 \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 "none" +msgstr "nichts" + +#: 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" diff --git a/locale/en/LC_MESSAGES/mcg.mo b/locale/en/LC_MESSAGES/mcg.mo index d1b8c35..d14c7d7 100644 Binary files a/locale/en/LC_MESSAGES/mcg.mo and b/locale/en/LC_MESSAGES/mcg.mo differ diff --git a/locale/en/LC_MESSAGES/mcg.po b/locale/en/LC_MESSAGES/mcg.po index bdc09fe..dfd501b 100644 --- a/locale/en/LC_MESSAGES/mcg.po +++ b/locale/en/LC_MESSAGES/mcg.po @@ -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 \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 "none" +msgstr "none" + +#: 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" diff --git a/mcg/client.py b/mcg/client.py index 2ddd75a..ca9102d 100644 --- a/mcg/client.py +++ b/mcg/client.py @@ -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'] diff --git a/mcg/widgets.py b/mcg/widgets.py index 88278ce..4e35103 100644 --- a/mcg/widgets.py +++ b/mcg/widgets.py @@ -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, ()),