mirror of
https://github.com/SoongNoonien/mpdevil.git
synced 2023-08-10 21:12:44 +03:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
02dec1f5f8 | ||
![]() |
4cc8c63ef9 | ||
![]() |
806b599db2 | ||
![]() |
279107d2a6 | ||
![]() |
a99ce64c66 | ||
![]() |
f24f58e930 | ||
![]() |
750a311327 | ||
![]() |
23f9ffa76c | ||
![]() |
6ed7a9c46d | ||
![]() |
98d1a140bf | ||
![]() |
76c27816a8 | ||
![]() |
b1994fc4bc | ||
![]() |
84216f569f | ||
![]() |
a7a209d577 | ||
![]() |
953d5ec8d3 | ||
![]() |
9ed5e67f5e | ||
![]() |
fdeaad4b9f | ||
![]() |
f6124ae45e | ||
![]() |
76b7b3b45b | ||
![]() |
7e8b169705 | ||
![]() |
3e2f8c51d2 | ||
![]() |
6acef07ccc | ||
![]() |
666fe08208 | ||
![]() |
7f2cccdbab | ||
![]() |
2369aca946 | ||
![]() |
22f452c4ef | ||
![]() |
d28b9901cb | ||
![]() |
142ff75774 | ||
![]() |
9869bd8753 | ||
![]() |
6f188128ae | ||
![]() |
f240215b1f | ||
![]() |
df3f996f71 | ||
![]() |
67c914002d | ||
![]() |
6198a821db | ||
![]() |
d07ca0697d | ||
![]() |
32893f7062 | ||
![]() |
aacf27ccb0 |
@@ -2,7 +2,7 @@ README for mpdevil
|
||||
==================
|
||||
mpdevil is a simple music browser for the Music Player Daemon (MPD) which is focused on playing local music without the need of managing playlists. Instead of maintaining a client side database of your music library, mpdevil loads all tags and covers on demand. So you'll never see any outdated information in the browser. mpdevil strongly relies on tags.
|
||||
|
||||

|
||||

|
||||
|
||||
Features
|
||||
--------
|
||||
|
@@ -3,13 +3,14 @@
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="modal">True</property>
|
||||
<property name="program_name">mpdevil</property>
|
||||
<property name="version">1.7.0</property>
|
||||
<property name="version">1.8.0</property>
|
||||
<property name="comments" translatable="yes">A simple music browser for MPD</property>
|
||||
<property name="authors">Martin Wagner</property>
|
||||
<property name="translator_credits">Martin de Reuver
|
||||
Georgi Kamenov
|
||||
Martin Wagner
|
||||
Oğuz Ersen</property>
|
||||
Oğuz Ersen
|
||||
Łukasz Drukała</property>
|
||||
<property name="website">https://github.com/SoongNoonien/mpdevil</property>
|
||||
<property name="copyright">Copyright © 2020-2022 Martin Wagner</property>
|
||||
<property name="license_type">gpl-3-0</property>
|
||||
|
@@ -19,12 +19,12 @@
|
||||
</ul>
|
||||
</description>
|
||||
<releases>
|
||||
<release version="1.7.0" date="2022-04-07"/>
|
||||
<release version="1.8.0" date="2022-09-11"/>
|
||||
</releases>
|
||||
<launchable type="desktop-id">org.mpdevil.mpdevil.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image type="source" width="1121" height="790">https://raw.githubusercontent.com/SoongNoonien/mpdevil/v1.7.0/screenshots/mainwindow_1.7.0.png</image>
|
||||
<image type="source" width="1121" height="790">https://raw.githubusercontent.com/SoongNoonien/mpdevil/v1.8.0/screenshots/mainwindow_1.8.0.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://github.com/SoongNoonien/mpdevil</url>
|
||||
|
@@ -16,6 +16,14 @@
|
||||
<default>691</default>
|
||||
<summary>Default height of window</summary>
|
||||
</key>
|
||||
<key type="i" name="mini-player-width">
|
||||
<default>400</default>
|
||||
<summary>Default width of mini player</summary>
|
||||
</key>
|
||||
<key type="i" name="mini-player-height">
|
||||
<default>447</default>
|
||||
<summary>Default height of mini player</summary>
|
||||
</key>
|
||||
<key type="i" name="paned0">
|
||||
<default>350</default>
|
||||
<summary>Default position of cover/playlist separator</summary>
|
||||
@@ -44,10 +52,6 @@
|
||||
<default>160</default>
|
||||
<summary>Size of covers in album view</summary>
|
||||
</key>
|
||||
<key type="i" name="track-cover">
|
||||
<default>350</default>
|
||||
<summary>Size of main cover</summary>
|
||||
</key>
|
||||
<key type="i" name="icon-size">
|
||||
<default>24</default>
|
||||
<summary>Size of icons in main control bar</summary>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
project('mpdevil', version: '1.7.0')
|
||||
project('mpdevil', version: '1.8.0')
|
||||
|
||||
i18n = import('i18n')
|
||||
gnome = import('gnome')
|
||||
|
@@ -1 +1 @@
|
||||
de nl bg tr
|
||||
de nl bg tr pl
|
||||
|
563
po/pl.po
Normal file
563
po/pl.po
Normal file
@@ -0,0 +1,563 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the mpdevil package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: mpdevil\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-04-07 12:12+0200\n"
|
||||
"PO-Revision-Date: 2022-06-20 19:15+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && "
|
||||
"(n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
"X-Generator: Poedit 3.1\n"
|
||||
|
||||
#: src/mpdevil.py:504
|
||||
#, python-brace-format
|
||||
msgid "{days} day"
|
||||
msgid_plural "{days} days"
|
||||
msgstr[0] "{days} dzień"
|
||||
msgstr[1] "{days} dni"
|
||||
msgstr[2] "{days} dni"
|
||||
|
||||
#: src/mpdevil.py:541
|
||||
#, python-brace-format
|
||||
msgid "{channels} channel"
|
||||
msgid_plural "{channels} channels"
|
||||
msgstr[0] "{channels} kanał"
|
||||
msgstr[1] "{channels} kanały"
|
||||
msgstr[2] "{channels} kanałów"
|
||||
|
||||
#: src/mpdevil.py:1012
|
||||
msgid "(restart required)"
|
||||
msgstr "(wymagane ponowne uruchomienie)"
|
||||
|
||||
#: src/mpdevil.py:1058
|
||||
msgid "Use Client-side decoration"
|
||||
msgstr "Używaj dekoracji po stronie klienta"
|
||||
|
||||
#: src/mpdevil.py:1059
|
||||
msgid "Show stop button"
|
||||
msgstr "Pokaż przycisk „stop”"
|
||||
|
||||
#: src/mpdevil.py:1060
|
||||
msgid "Show audio format"
|
||||
msgstr "Pokaż format audio"
|
||||
|
||||
#: src/mpdevil.py:1061
|
||||
msgid "Show lyrics button"
|
||||
msgstr "Pokaż przycisk tekstu utworu"
|
||||
|
||||
#: src/mpdevil.py:1062
|
||||
msgid "Place playlist at the side"
|
||||
msgstr "Umieść playlistę z boku"
|
||||
|
||||
#: src/mpdevil.py:1068
|
||||
msgid "Main cover size"
|
||||
msgstr "Rozmiar głównej okładki"
|
||||
|
||||
#: src/mpdevil.py:1069
|
||||
msgid "Album view cover size"
|
||||
msgstr "Rozmiar okładek w przeglądarce"
|
||||
|
||||
#: src/mpdevil.py:1070
|
||||
msgid "Action bar icon size"
|
||||
msgstr "Rozmiar ikon na pasku akcji"
|
||||
|
||||
#: src/mpdevil.py:1080
|
||||
msgid "Support “MPRIS”"
|
||||
msgstr "Wspieraj „MPRIS”"
|
||||
|
||||
#: src/mpdevil.py:1081
|
||||
msgid "Sort albums by year"
|
||||
msgstr "Sortuj albumy według roku"
|
||||
|
||||
#: src/mpdevil.py:1082
|
||||
msgid "Send notification on title change"
|
||||
msgstr "Wysyłaj powiadomienie przy zmianie utworu"
|
||||
|
||||
#: src/mpdevil.py:1083
|
||||
msgid "Play selected albums and titles immediately"
|
||||
msgstr "Odtwarzaj wybrane albumy i utwory od razu"
|
||||
|
||||
#: src/mpdevil.py:1084
|
||||
msgid "Rewind via previous button"
|
||||
msgstr "Przewijaj w tył za pomocą przycisku „poprzedni”"
|
||||
|
||||
#: src/mpdevil.py:1085
|
||||
msgid "Stop playback on quit"
|
||||
msgstr "Zatrzymaj odtwarzanie przy wyjściu"
|
||||
|
||||
#: src/mpdevil.py:1112
|
||||
msgid "Choose directory"
|
||||
msgstr "Wybierz katalog"
|
||||
|
||||
#: src/mpdevil.py:1125
|
||||
msgid "Connect via Unix domain socket"
|
||||
msgstr "Połącz się poprzez Unix domain socket"
|
||||
|
||||
#: src/mpdevil.py:1144
|
||||
msgid ""
|
||||
"The first image in the same directory as the song file matching this regex "
|
||||
"will be displayed. %AlbumArtist% and %Album% will be replaced by the "
|
||||
"corresponding tags of the song."
|
||||
msgstr ""
|
||||
"Pierwszy obrazek w tym samym katalogu co plik utworu, który pasuje do tego "
|
||||
"wyrażenia regularnego, zostanie wyświetlony. %AlbumArtist% oraz %Album% "
|
||||
"zostaną zastąpione odpowiednimi tagami utworu."
|
||||
|
||||
#: src/mpdevil.py:1149
|
||||
msgid "Socket:"
|
||||
msgstr "Socket:"
|
||||
|
||||
#: src/mpdevil.py:1151
|
||||
msgid "Host:"
|
||||
msgstr "Host:"
|
||||
|
||||
#: src/mpdevil.py:1153
|
||||
msgid "Password:"
|
||||
msgstr "Hasło:"
|
||||
|
||||
#: src/mpdevil.py:1154
|
||||
msgid "Music lib:"
|
||||
msgstr "Biblioteka muzyczna:"
|
||||
|
||||
#: src/mpdevil.py:1156
|
||||
msgid "Cover regex:"
|
||||
msgstr "Wyrażenie regularne okładki:"
|
||||
|
||||
#: src/mpdevil.py:1180 src/mpdevil.py:3560
|
||||
msgid "Profile 1"
|
||||
msgstr "Profil 1"
|
||||
|
||||
#: src/mpdevil.py:1181 src/mpdevil.py:3560
|
||||
msgid "Profile 2"
|
||||
msgstr "Profil 2"
|
||||
|
||||
#: src/mpdevil.py:1182 src/mpdevil.py:3560
|
||||
msgid "Profile 3"
|
||||
msgstr "Profil 3"
|
||||
|
||||
#. connect button
|
||||
#: src/mpdevil.py:1186 src/mpdevil.py:3448
|
||||
msgid "Connect"
|
||||
msgstr "Połącz się"
|
||||
|
||||
#: src/mpdevil.py:1209 src/mpdevil.py:1211 src/mpdevil.py:3449
|
||||
#: src/mpdevil.py:3552
|
||||
msgid "Preferences"
|
||||
msgstr "Preferencje"
|
||||
|
||||
#: src/mpdevil.py:1224 src/mpdevil.py:1234
|
||||
msgid "View"
|
||||
msgstr "Widok"
|
||||
|
||||
#: src/mpdevil.py:1225 src/mpdevil.py:1235
|
||||
msgid "Behavior"
|
||||
msgstr "Zachowanie"
|
||||
|
||||
#: src/mpdevil.py:1226 src/mpdevil.py:1236
|
||||
msgid "Profiles"
|
||||
msgstr "Profile"
|
||||
|
||||
#: src/mpdevil.py:1253
|
||||
msgid "Stats"
|
||||
msgstr "Statystyki"
|
||||
|
||||
#: src/mpdevil.py:1262
|
||||
msgid "<b>Protocol:</b>"
|
||||
msgstr "<b>Protokół:</b>"
|
||||
|
||||
#: src/mpdevil.py:1263
|
||||
msgid "<b>Uptime:</b>"
|
||||
msgstr "<b>Czas działania:</b>"
|
||||
|
||||
#: src/mpdevil.py:1264
|
||||
msgid "<b>Playtime:</b>"
|
||||
msgstr "<b>Czas odtwarzania:</b>"
|
||||
|
||||
#: src/mpdevil.py:1265
|
||||
msgid "<b>Artists:</b>"
|
||||
msgstr "<b>Artyści:</b>"
|
||||
|
||||
#: src/mpdevil.py:1266
|
||||
msgid "<b>Albums:</b>"
|
||||
msgstr "<b>Albumy:</b>"
|
||||
|
||||
#: src/mpdevil.py:1267
|
||||
msgid "<b>Songs:</b>"
|
||||
msgstr "<b>Utwory:</b>"
|
||||
|
||||
#: src/mpdevil.py:1268
|
||||
msgid "<b>Total Playtime:</b>"
|
||||
msgstr "<b>Całkowity czas odtwarzania:</b>"
|
||||
|
||||
#: src/mpdevil.py:1269
|
||||
msgid "<b>Database Update:</b>"
|
||||
msgstr "<b>Aktualizacja bazy danych:</b>"
|
||||
|
||||
#: src/mpdevil.py:1348
|
||||
msgid "Add to playlist"
|
||||
msgstr "Dodaj do playlisty"
|
||||
|
||||
#: src/mpdevil.py:1351
|
||||
msgid "Show in file manager"
|
||||
msgstr "Pokaż w menedżerze plików"
|
||||
|
||||
#: src/mpdevil.py:1355 src/mpdevil.py:1590 src/mpdevil.py:2304
|
||||
msgid "Append"
|
||||
msgstr "Dodaj"
|
||||
|
||||
#: src/mpdevil.py:1356 src/mpdevil.py:1591 src/mpdevil.py:2305
|
||||
msgid "Play"
|
||||
msgstr "Odtwórz"
|
||||
|
||||
#: src/mpdevil.py:1357 src/mpdevil.py:1592 src/mpdevil.py:2306
|
||||
msgid "Enqueue"
|
||||
msgstr "Dodaj do kolejki"
|
||||
|
||||
#: src/mpdevil.py:1374
|
||||
msgid "MPD-Tag"
|
||||
msgstr "Tag MPD"
|
||||
|
||||
#: src/mpdevil.py:1377
|
||||
msgid "Value"
|
||||
msgstr "Wartość"
|
||||
|
||||
#: src/mpdevil.py:1448 src/mpdevil.py:2510
|
||||
msgid "No"
|
||||
msgstr "Nie"
|
||||
|
||||
#. the order of weight_set and weight seems to be important here
|
||||
#: src/mpdevil.py:1449 src/mpdevil.py:2512
|
||||
msgid "Title"
|
||||
msgstr "Tytuł"
|
||||
|
||||
#: src/mpdevil.py:1450 src/mpdevil.py:2513
|
||||
msgid "Length"
|
||||
msgstr "Długość"
|
||||
|
||||
#: src/mpdevil.py:1463
|
||||
msgid "Add all titles to playlist"
|
||||
msgstr "Dodaj wszystkie utwory do playlisty"
|
||||
|
||||
#: src/mpdevil.py:1464
|
||||
msgid "Directly play all titles"
|
||||
msgstr "Bezpośrednio odtwórz wszystkie utwory"
|
||||
|
||||
#: src/mpdevil.py:1465
|
||||
msgid ""
|
||||
"Append all titles after the currently playing track and clear the playlist "
|
||||
"from all other songs"
|
||||
msgstr ""
|
||||
"Dodaj wszystkie utwory po aktualnie odtwarzanym i usuń wszystkie inne z "
|
||||
"playlisty"
|
||||
|
||||
#: src/mpdevil.py:1559 src/mpdevil.py:2394 src/mpdevil.py:2671
|
||||
#, python-brace-format
|
||||
msgid "{number} song ({duration})"
|
||||
msgid_plural "{number} songs ({duration})"
|
||||
msgstr[0] "{number} utwór ({duration})"
|
||||
msgstr[1] "{number} utwory ({duration})"
|
||||
msgstr[2] "{number} utworów ({duration})"
|
||||
|
||||
#: src/mpdevil.py:1658
|
||||
#, python-brace-format
|
||||
msgid "{hits} hit"
|
||||
msgid_plural "{hits} hits"
|
||||
msgstr[0] "{hits} trafienie"
|
||||
msgstr[1] "{hits} trafienia"
|
||||
msgstr[2] "{hits} trafień"
|
||||
|
||||
#: src/mpdevil.py:1743
|
||||
msgid "all tags"
|
||||
msgstr "wszystkie tagi"
|
||||
|
||||
#: src/mpdevil.py:1875
|
||||
msgid "all genres"
|
||||
msgstr "wszystkie gatunki"
|
||||
|
||||
#: src/mpdevil.py:1901
|
||||
msgid "all artists"
|
||||
msgstr "wszyscy artyści"
|
||||
|
||||
#: src/mpdevil.py:2314
|
||||
msgid "Save"
|
||||
msgstr "Zapisz"
|
||||
|
||||
#: src/mpdevil.py:2318
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: src/mpdevil.py:2439 data/ShortcutsWindow.ui:240
|
||||
msgid "Clear playlist"
|
||||
msgstr "Wyczyść playlistę"
|
||||
|
||||
#: src/mpdevil.py:2701
|
||||
msgid "Scroll to current song"
|
||||
msgstr "Przewiń do aktualnego utworu"
|
||||
|
||||
#: src/mpdevil.py:2713
|
||||
msgid "Playlists"
|
||||
msgstr "Playlisty"
|
||||
|
||||
#: src/mpdevil.py:2814
|
||||
msgid "searching…"
|
||||
msgstr "wyszukiwanie…"
|
||||
|
||||
#: src/mpdevil.py:2819
|
||||
msgid "connection error"
|
||||
msgstr "błąd połączenia"
|
||||
|
||||
#: src/mpdevil.py:2821
|
||||
msgid "lyrics not found"
|
||||
msgstr "nie znaleziono tekstu utworu"
|
||||
|
||||
#: src/mpdevil.py:2924
|
||||
msgid "Lyrics"
|
||||
msgstr "Tekst utworu"
|
||||
|
||||
#: src/mpdevil.py:3015 src/mpdevil.py:3016
|
||||
#, python-brace-format
|
||||
msgid "{number} song"
|
||||
msgid_plural "{number} songs"
|
||||
msgstr[0] "{number} utwór"
|
||||
msgstr[1] "{number} utwory"
|
||||
msgstr[2] "{number} utworów"
|
||||
|
||||
#: src/mpdevil.py:3201
|
||||
msgid "Repeat mode"
|
||||
msgstr "Tryb powtarzania"
|
||||
|
||||
#: src/mpdevil.py:3202
|
||||
msgid "Random mode"
|
||||
msgstr "Tryb losowy"
|
||||
|
||||
#: src/mpdevil.py:3203
|
||||
msgid "Single mode"
|
||||
msgstr "Tryb pojedynczy"
|
||||
|
||||
#: src/mpdevil.py:3204
|
||||
msgid "Consume mode"
|
||||
msgstr "Tryb wyczerpywania"
|
||||
|
||||
#: src/mpdevil.py:3418
|
||||
msgid "Updating Database…"
|
||||
msgstr "Aktualizowanie bazy danych…"
|
||||
|
||||
#: src/mpdevil.py:3470
|
||||
#, python-brace-format
|
||||
msgid "Connection to “{socket}” failed"
|
||||
msgstr "Nie udało połączyć się do „{socket}”"
|
||||
|
||||
#: src/mpdevil.py:3472
|
||||
#, python-brace-format
|
||||
msgid "Connection to “{host}:{port}” failed"
|
||||
msgstr "Nie udało połączyć się do „{host}:{port}”"
|
||||
|
||||
#: src/mpdevil.py:3537
|
||||
msgid "Search"
|
||||
msgstr "Szukaj"
|
||||
|
||||
#: src/mpdevil.py:3540 data/ShortcutsWindow.ui:99
|
||||
msgid "Back to current album"
|
||||
msgstr "Wróć do aktualnego albumu"
|
||||
|
||||
#: src/mpdevil.py:3553
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "Skróty klawiszowe"
|
||||
|
||||
#: src/mpdevil.py:3554
|
||||
msgid "Help"
|
||||
msgstr "Pomoc"
|
||||
|
||||
#: src/mpdevil.py:3555
|
||||
msgid "About mpdevil"
|
||||
msgstr "O mpdevil"
|
||||
|
||||
#: src/mpdevil.py:3557
|
||||
msgid "Update Database"
|
||||
msgstr "Aktualizuj bazę danych"
|
||||
|
||||
#: src/mpdevil.py:3558
|
||||
msgid "Server Stats"
|
||||
msgstr "Statystyki serwera"
|
||||
|
||||
#: src/mpdevil.py:3565
|
||||
msgid "Mini Player"
|
||||
msgstr "Mini odtwarzacz"
|
||||
|
||||
#: src/mpdevil.py:3566
|
||||
msgid "Genre Filter"
|
||||
msgstr "Filtr gatunku"
|
||||
|
||||
#: src/mpdevil.py:3576
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
#: src/mpdevil.py:3629 src/mpdevil.py:3631
|
||||
msgid "connecting…"
|
||||
msgstr "łączenie…"
|
||||
|
||||
#: src/mpdevil.py:3790
|
||||
msgid "Debug mode"
|
||||
msgstr "Tryb debugowania"
|
||||
|
||||
#: data/org.mpdevil.mpdevil.desktop.in:3
|
||||
msgid "mpdevil"
|
||||
msgstr "mpdevil"
|
||||
|
||||
#: data/org.mpdevil.mpdevil.desktop.in:4
|
||||
msgid "MPD Client"
|
||||
msgstr "Klient MPD"
|
||||
|
||||
#: data/org.mpdevil.mpdevil.desktop.in:5 data/AboutDialog.ui:7
|
||||
msgid "A simple music browser for MPD"
|
||||
msgstr "Prosta przeglądarka muzyki do MPD"
|
||||
|
||||
#: data/ShortcutsWindow.ui:12
|
||||
msgid "General"
|
||||
msgstr "Ogólne"
|
||||
|
||||
#: data/ShortcutsWindow.ui:16
|
||||
msgid "Open online help"
|
||||
msgstr "Otwórz pomoc online"
|
||||
|
||||
#: data/ShortcutsWindow.ui:23
|
||||
msgid "Open shortcuts window"
|
||||
msgstr "Otwórz okno skrótów klawiszowych"
|
||||
|
||||
#: data/ShortcutsWindow.ui:30
|
||||
msgid "Open menu"
|
||||
msgstr "Otwórz menu"
|
||||
|
||||
#: data/ShortcutsWindow.ui:37
|
||||
msgid "Update database"
|
||||
msgstr "Aktualizuj bazę danych"
|
||||
|
||||
#: data/ShortcutsWindow.ui:44
|
||||
msgid "Quit"
|
||||
msgstr "Wyjdź"
|
||||
|
||||
#: data/ShortcutsWindow.ui:53
|
||||
msgid "Window"
|
||||
msgstr "Okno"
|
||||
|
||||
#: data/ShortcutsWindow.ui:57
|
||||
msgid "Cycle through profiles"
|
||||
msgstr "Przełącz między profilami"
|
||||
|
||||
#: data/ShortcutsWindow.ui:64
|
||||
msgid "Cycle through profiles in reversed order"
|
||||
msgstr "Przełącz między profilami w odwrotnej kolejności"
|
||||
|
||||
#: data/ShortcutsWindow.ui:71
|
||||
msgid "Toggle mini player"
|
||||
msgstr "Włącz/wyłącz mini odtwarzacz"
|
||||
|
||||
#: data/ShortcutsWindow.ui:78
|
||||
msgid "Toggle genre filter"
|
||||
msgstr "Włącz/wyłącz filtr gatunku"
|
||||
|
||||
#: data/ShortcutsWindow.ui:85
|
||||
msgid "Toggle lyrics"
|
||||
msgstr "Włącz/wyłącz tekst utworu"
|
||||
|
||||
#: data/ShortcutsWindow.ui:92
|
||||
msgid "Toggle search"
|
||||
msgstr "Włącz/wyłącz wyszukiwanie"
|
||||
|
||||
#: data/ShortcutsWindow.ui:108
|
||||
msgid "Playback"
|
||||
msgstr "Odtwarzanie"
|
||||
|
||||
#: data/ShortcutsWindow.ui:112
|
||||
msgid "Play/Pause"
|
||||
msgstr "Odtwórz/Pauza"
|
||||
|
||||
#: data/ShortcutsWindow.ui:119
|
||||
msgid "Stop"
|
||||
msgstr "Stop"
|
||||
|
||||
#: data/ShortcutsWindow.ui:126
|
||||
msgid "Stop after current title"
|
||||
msgstr "Stop po tym utworze"
|
||||
|
||||
#: data/ShortcutsWindow.ui:133
|
||||
msgid "Next title"
|
||||
msgstr "Następny utwór"
|
||||
|
||||
#: data/ShortcutsWindow.ui:140
|
||||
msgid "Previous title"
|
||||
msgstr "Poprzedni utwór"
|
||||
|
||||
#: data/ShortcutsWindow.ui:147
|
||||
msgid "Seek forward"
|
||||
msgstr "Przewiń do przodu"
|
||||
|
||||
#: data/ShortcutsWindow.ui:154
|
||||
msgid "Seek backward"
|
||||
msgstr "Przewiń do tyłu"
|
||||
|
||||
#: data/ShortcutsWindow.ui:161
|
||||
msgid "Toggle repeat mode"
|
||||
msgstr "Włącz/wyłącz tryb powtarzania"
|
||||
|
||||
#: data/ShortcutsWindow.ui:168
|
||||
msgid "Toggle random mode"
|
||||
msgstr "Włącz/wyłącz tryb losowy"
|
||||
|
||||
#: data/ShortcutsWindow.ui:175
|
||||
msgid "Toggle single mode"
|
||||
msgstr "Włącz/wyłącz tryb pojedynczy"
|
||||
|
||||
#: data/ShortcutsWindow.ui:182
|
||||
msgid "Toggle consume mode"
|
||||
msgstr "Włącz/wyłącz tryb wyczerpywania"
|
||||
|
||||
#: data/ShortcutsWindow.ui:191
|
||||
msgid "Search, Album Dialog, Album List and Artist List"
|
||||
msgstr "Wyszukiwanie, Okno albumu, Lista albumów i Lista artystów"
|
||||
|
||||
#: data/ShortcutsWindow.ui:195
|
||||
msgid "Enqueue selected item"
|
||||
msgstr "Dodaj wybraną pozycję do kolejki"
|
||||
|
||||
#: data/ShortcutsWindow.ui:202
|
||||
msgid "Append selected item"
|
||||
msgstr "Dodaj wybraną pozycję"
|
||||
|
||||
#: data/ShortcutsWindow.ui:203 data/ShortcutsWindow.ui:233
|
||||
msgid "Middle-click"
|
||||
msgstr "Kółko myszy"
|
||||
|
||||
#: data/ShortcutsWindow.ui:210
|
||||
msgid "Play selected item immediately"
|
||||
msgstr "Odtwórz wybraną pozycję natychmiast"
|
||||
|
||||
#: data/ShortcutsWindow.ui:211
|
||||
msgid "Double-click"
|
||||
msgstr "Podwójne kliknięcie"
|
||||
|
||||
#: data/ShortcutsWindow.ui:218 data/ShortcutsWindow.ui:247
|
||||
msgid "Show additional information"
|
||||
msgstr "Wyświetl dodatkowe informacje"
|
||||
|
||||
#: data/ShortcutsWindow.ui:219 data/ShortcutsWindow.ui:248
|
||||
msgid "Right-click"
|
||||
msgstr "Prawy przycisk myszy"
|
||||
|
||||
#: data/ShortcutsWindow.ui:228
|
||||
msgid "Playlist"
|
||||
msgstr "Playlista"
|
||||
|
||||
#: data/ShortcutsWindow.ui:232
|
||||
msgid "Remove selected song"
|
||||
msgstr "Usuń wybrany utwór"
|
Binary file not shown.
Before Width: | Height: | Size: 979 KiB |
BIN
screenshots/mainwindow_1.8.0.png
Normal file
BIN
screenshots/mainwindow_1.8.0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 896 KiB |
445
src/mpdevil.py
445
src/mpdevil.py
@@ -28,10 +28,10 @@ import urllib.error
|
||||
import threading
|
||||
import functools
|
||||
import itertools
|
||||
import datetime
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import re
|
||||
import locale
|
||||
from gettext import gettext as _, ngettext, textdomain, bindtextdomain
|
||||
@@ -56,7 +56,7 @@ FALLBACK_LIB=GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC)
|
||||
############################
|
||||
|
||||
def idle_add(*args, **kwargs):
|
||||
GLib.idle_add(*args, priority=GLib.PRIORITY_DEFAULT , **kwargs)
|
||||
GLib.idle_add(*args, priority=GLib.PRIORITY_DEFAULT, **kwargs)
|
||||
|
||||
def main_thread_function(func):
|
||||
@functools.wraps(func)
|
||||
@@ -329,16 +329,17 @@ class MPRISInterface: # TODO emit Seeked if needed
|
||||
setter(value)
|
||||
|
||||
def GetAll(self, interface_name):
|
||||
read_props={}
|
||||
try:
|
||||
props=self._prop_mapping[interface_name]
|
||||
except KeyError: # interface has no properties
|
||||
return {}
|
||||
else:
|
||||
read_props={}
|
||||
for key, (getter, setter) in props.items():
|
||||
if callable(getter):
|
||||
getter=getter()
|
||||
read_props[key]=getter
|
||||
except KeyError: # interface has no properties
|
||||
pass
|
||||
return read_props
|
||||
return read_props
|
||||
|
||||
def PropertiesChanged(self, interface_name, changed_properties, invalidated_properties):
|
||||
self._bus.emit_signal(
|
||||
@@ -362,7 +363,7 @@ class MPRISInterface: # TODO emit Seeked if needed
|
||||
self._client.next()
|
||||
|
||||
def Previous(self):
|
||||
self._client.conditional_previous()
|
||||
self._client.previous()
|
||||
|
||||
def Pause(self):
|
||||
self._client.pause(1)
|
||||
@@ -481,42 +482,39 @@ class MPRISInterface: # TODO emit Seeked if needed
|
||||
######################
|
||||
|
||||
class Duration():
|
||||
def __init__(self, value=None):
|
||||
if value is None:
|
||||
def __init__(self, seconds=None):
|
||||
if seconds is None:
|
||||
self._fallback=True
|
||||
self._value=0.0
|
||||
self._seconds=0.0
|
||||
else:
|
||||
self._fallback=False
|
||||
self._value=float(value)
|
||||
self._seconds=float(seconds)
|
||||
|
||||
def __str__(self):
|
||||
if self._fallback:
|
||||
return "‒‒∶‒‒"
|
||||
else:
|
||||
if self._value < 0:
|
||||
sign="−"
|
||||
value=-int(self._value)
|
||||
seconds=int(self._seconds)
|
||||
days,seconds=divmod(seconds, 86400) # 86400 seconds make a day
|
||||
hours,seconds=divmod(seconds, 3600) # 3600 seconds make an hour
|
||||
minutes,seconds=divmod(seconds, 60)
|
||||
if days > 0:
|
||||
days_string=ngettext("{days} day", "{days} days", days).format(days=days)
|
||||
return f"{days_string}, {hours:02d}∶{minutes:02d}∶{seconds:02d}"
|
||||
elif hours > 0:
|
||||
return f"{hours}∶{minutes:02d}∶{seconds:02d}"
|
||||
else:
|
||||
sign=""
|
||||
value=int(self._value)
|
||||
delta=datetime.timedelta(seconds=value)
|
||||
if delta.days > 0:
|
||||
days=ngettext("{days} day", "{days} days", delta.days).format(days=delta.days)
|
||||
time_string=f"{days}, {datetime.timedelta(seconds=delta.seconds)}"
|
||||
else:
|
||||
time_string=str(delta).lstrip("0").lstrip(":")
|
||||
return sign+time_string.replace(":", "∶") # use 'ratio' as delimiter
|
||||
return f"{minutes:02d}∶{seconds:02d}"
|
||||
|
||||
def __float__(self):
|
||||
return self._value
|
||||
return self._seconds
|
||||
|
||||
class LastModified():
|
||||
def __init__(self, date):
|
||||
self._date=date
|
||||
|
||||
def __str__(self):
|
||||
time=datetime.datetime.strptime(self._date, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return time.strftime("%a %d %B %Y, %H∶%M UTC")
|
||||
return GLib.DateTime.new_from_iso8601(self._date).to_local().format("%a %d %B %Y, %H∶%M")
|
||||
|
||||
def raw(self):
|
||||
return self._date
|
||||
@@ -599,23 +597,27 @@ class Song(collections.UserDict):
|
||||
return f"{title}\n<small>{GLib.markup_escape_text(self.get_album_with_date())}</small>"
|
||||
|
||||
class BinaryCover(bytes):
|
||||
def get_pixbuf(self, size):
|
||||
def get_pixbuf(self, size=-1):
|
||||
loader=GdkPixbuf.PixbufLoader()
|
||||
try:
|
||||
loader.write(self)
|
||||
loader.close()
|
||||
raw_pixbuf=loader.get_pixbuf()
|
||||
ratio=raw_pixbuf.get_width()/raw_pixbuf.get_height()
|
||||
if ratio > 1:
|
||||
pixbuf=raw_pixbuf.scale_simple(size,size/ratio,GdkPixbuf.InterpType.BILINEAR)
|
||||
else:
|
||||
pixbuf=raw_pixbuf.scale_simple(size*ratio,size,GdkPixbuf.InterpType.BILINEAR)
|
||||
except gi.repository.GLib.Error: # load fallback if cover can't be loaded
|
||||
pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size)
|
||||
else:
|
||||
loader.close()
|
||||
if size == -1:
|
||||
pixbuf=loader.get_pixbuf()
|
||||
else:
|
||||
raw_pixbuf=loader.get_pixbuf()
|
||||
ratio=raw_pixbuf.get_width()/raw_pixbuf.get_height()
|
||||
if ratio > 1:
|
||||
pixbuf=raw_pixbuf.scale_simple(size,size/ratio,GdkPixbuf.InterpType.BILINEAR)
|
||||
else:
|
||||
pixbuf=raw_pixbuf.scale_simple(size*ratio,size,GdkPixbuf.InterpType.BILINEAR)
|
||||
return pixbuf
|
||||
|
||||
class FileCover(str):
|
||||
def get_pixbuf(self, size):
|
||||
def get_pixbuf(self, size=-1):
|
||||
try:
|
||||
pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(self, size, size)
|
||||
except gi.repository.GLib.Error: # load fallback if cover can't be loaded
|
||||
@@ -704,7 +706,6 @@ class Client(MPDClient):
|
||||
return [Song(song) for song in super().listplaylistinfo(name)]
|
||||
|
||||
def start(self):
|
||||
self.emitter.emit("disconnected") # bring player in defined state
|
||||
profile=self._settings.get_active_profile()
|
||||
if profile.get_boolean("socket-connection"):
|
||||
socket=profile.get_string("socket")
|
||||
@@ -718,6 +719,7 @@ class Client(MPDClient):
|
||||
if profile.get_string("password"):
|
||||
self.password(profile.get_string("password"))
|
||||
except:
|
||||
self.emitter.emit("disconnected")
|
||||
self.emitter.emit("connection_error")
|
||||
return False
|
||||
# connect successful
|
||||
@@ -733,6 +735,7 @@ class Client(MPDClient):
|
||||
return True
|
||||
else:
|
||||
self.disconnect()
|
||||
self.emitter.emit("disconnected")
|
||||
self.emitter.emit("connection_error")
|
||||
print("No read permission, check your mpd config.")
|
||||
return False
|
||||
@@ -1065,7 +1068,6 @@ class ViewSettings(SettingsList):
|
||||
row=ToggleRow(label, settings, key, restart_required)
|
||||
self.append(row)
|
||||
int_data=(
|
||||
(_("Main cover size"), (100, 1200, 10), "track-cover"),
|
||||
(_("Album view cover size"), (50, 600, 10), "album-cover"),
|
||||
(_("Action bar icon size"), (16, 64, 2), "icon-size"),
|
||||
)
|
||||
@@ -1210,7 +1212,6 @@ class SettingsDialog(Gtk.Dialog):
|
||||
else:
|
||||
super().__init__(title=_("Preferences"), transient_for=parent)
|
||||
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
|
||||
self.set_default_size(500, 400)
|
||||
|
||||
# widgets
|
||||
view=ViewSettings(settings)
|
||||
@@ -1272,7 +1273,7 @@ class ServerStats(Gtk.Dialog):
|
||||
stats["protocol"]=str(client.mpd_version)
|
||||
for key in ("uptime","playtime","db_playtime"):
|
||||
stats[key]=str(Duration(stats[key]))
|
||||
stats["db_update"]=str(datetime.datetime.fromtimestamp(int(stats["db_update"]))).replace(":", "∶")
|
||||
stats["db_update"]=GLib.DateTime.new_from_unix_local(int(stats["db_update"])).format("%a %d %B %Y, %H∶%M")
|
||||
|
||||
for i, key in enumerate(("protocol","uptime","playtime","db_update","db_playtime","artists","albums","songs")):
|
||||
grid.attach(Gtk.Label(label=display_str[key], use_markup=True, xalign=1), 0, i, 1, 1)
|
||||
@@ -1300,6 +1301,17 @@ class TreeView(Gtk.TreeView):
|
||||
rect.x,rect.y=self.convert_tree_to_widget_coords(rect.x,rect.y)
|
||||
return (rect.x+rect.width//2, max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y))
|
||||
|
||||
def save_set_cursor(self, *args, **kwargs):
|
||||
# The standard set_cursor function should scroll normally, but it dosen't work as it should when the treeview is not completely
|
||||
# initialized. This usually happens when the program is freshly started and the treeview isn't done with its internal tasks.
|
||||
# See: https://lazka.github.io/pgi-docs/GLib-2.0/constants.html#GLib.PRIORITY_HIGH_IDLE
|
||||
# Running set_cursor with a lower priority ensures that the treeview is done before it gets scrolled.
|
||||
GLib.idle_add(self.set_cursor, *args, **kwargs)
|
||||
|
||||
def save_scroll_to_cell(self, *args, **kwargs):
|
||||
# Similar problem as above.
|
||||
GLib.idle_add(self.scroll_to_cell, *args, **kwargs)
|
||||
|
||||
class AutoSizedIcon(Gtk.Image):
|
||||
def __init__(self, icon_name, settings_key, settings):
|
||||
super().__init__(icon_name=icon_name)
|
||||
@@ -1505,13 +1517,12 @@ class SongsList(TreeView):
|
||||
self.emit("button-clicked")
|
||||
|
||||
def show_info(self):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
path=self._store.get_path(treeiter)
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
self._song_popover.open(self._store[path][3], self, *self.get_popover_point(path))
|
||||
|
||||
def add_to_playlist(self, mode):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
self._client.files_to_playlist([self._store.get_value(treeiter, 3)], mode)
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
self._client.files_to_playlist([self._store[path][3]], mode)
|
||||
|
||||
class AlbumPopover(Gtk.Popover):
|
||||
def __init__(self, client, settings):
|
||||
@@ -1554,7 +1565,7 @@ class AlbumPopover(Gtk.Popover):
|
||||
self._songs_list.clear()
|
||||
tag_filter=("albumartist", albumartist, "album", album, "date", date)
|
||||
count=self._client.count(*tag_filter)
|
||||
duration=str(Duration(float(count["playtime"])))
|
||||
duration=str(Duration(count["playtime"]))
|
||||
length=int(count["songs"])
|
||||
text=ngettext("{number} song ({duration})", "{number} songs ({duration})", length).format(number=length, duration=duration)
|
||||
self._label.set_text(text)
|
||||
@@ -1780,35 +1791,36 @@ class SearchWindow(Gtk.Box):
|
||||
class SelectionList(TreeView):
|
||||
__gsignals__={"item-selected": (GObject.SignalFlags.RUN_FIRST, None, ()), "clear": (GObject.SignalFlags.RUN_FIRST, None, ())}
|
||||
def __init__(self, select_all_string):
|
||||
super().__init__(activate_on_single_click=True, search_column=0, headers_visible=False, fixed_height_mode=True)
|
||||
super().__init__(search_column=0, headers_visible=False, fixed_height_mode=True)
|
||||
self.select_all_string=select_all_string
|
||||
self._selected_path=None
|
||||
|
||||
# store
|
||||
# item, weight, initial-letter, weight-initials
|
||||
self._store=Gtk.ListStore(str, Pango.Weight, str, Pango.Weight)
|
||||
self._store.append([self.select_all_string, Pango.Weight.NORMAL, "", Pango.Weight.NORMAL])
|
||||
# item, initial-letter, weight-initials
|
||||
self._store=Gtk.ListStore(str, str, Pango.Weight)
|
||||
self._store.append([self.select_all_string, "", Pango.Weight.NORMAL])
|
||||
self.set_model(self._store)
|
||||
self._selection=self.get_selection()
|
||||
self._selection.set_mode(Gtk.SelectionMode.BROWSE)
|
||||
|
||||
# columns
|
||||
renderer_text_malign=Gtk.CellRendererText(xalign=0.5)
|
||||
self._column_initial=Gtk.TreeViewColumn("", renderer_text_malign, text=2, weight=3)
|
||||
self._column_initial=Gtk.TreeViewColumn("", renderer_text_malign, text=1, weight=2)
|
||||
self._column_initial.set_property("sizing", Gtk.TreeViewColumnSizing.FIXED)
|
||||
self._column_initial.set_property("min-width", 30)
|
||||
self.append_column(self._column_initial)
|
||||
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True, ypad=6)
|
||||
self._column_item=Gtk.TreeViewColumn("", renderer_text, text=0, weight=1)
|
||||
self._column_item=Gtk.TreeViewColumn("", renderer_text, text=0)
|
||||
self._column_item.set_property("sizing", Gtk.TreeViewColumnSizing.FIXED)
|
||||
self._column_item.set_property("expand", True)
|
||||
self.append_column(self._column_item)
|
||||
|
||||
# connect
|
||||
self.connect("row-activated", self._on_row_activated)
|
||||
self._selection.connect("changed", self._on_selection_changed)
|
||||
|
||||
def clear(self):
|
||||
self._store.clear()
|
||||
self._store.append([self.select_all_string, Pango.Weight.NORMAL, "", Pango.Weight.NORMAL])
|
||||
self._store.append([self.select_all_string, "", Pango.Weight.NORMAL])
|
||||
self._selected_path=None
|
||||
self.emit("clear")
|
||||
|
||||
@@ -1822,7 +1834,7 @@ class SelectionList(TreeView):
|
||||
if item[0] is None:
|
||||
char=item[1]
|
||||
else:
|
||||
self._store.insert_with_valuesv(-1, range(4), [item[0], Pango.Weight.NORMAL, char, Pango.Weight.BOLD])
|
||||
self._store.insert_with_valuesv(-1, range(3), [item[0], char, Pango.Weight.BOLD])
|
||||
char=""
|
||||
|
||||
def get_item_at_path(self, path):
|
||||
@@ -1835,8 +1847,7 @@ class SelectionList(TreeView):
|
||||
return len(self._store)-1
|
||||
|
||||
def select_path(self, path):
|
||||
self.set_cursor(path, None, False)
|
||||
self.row_activated(path, self._column_item)
|
||||
self._selection.select_path(path)
|
||||
|
||||
def select(self, item):
|
||||
row_num=len(self._store)
|
||||
@@ -1847,8 +1858,7 @@ class SelectionList(TreeView):
|
||||
break
|
||||
|
||||
def select_all(self):
|
||||
self.set_cursor(Gtk.TreePath(0), None, False)
|
||||
self.row_activated(Gtk.TreePath(0), self._column_item)
|
||||
self.select_path(Gtk.TreePath(0))
|
||||
|
||||
def get_path_selected(self):
|
||||
if self._selected_path is None:
|
||||
@@ -1859,16 +1869,15 @@ class SelectionList(TreeView):
|
||||
def get_item_selected(self):
|
||||
return self.get_item_at_path(self.get_path_selected())
|
||||
|
||||
def highlight_selected(self):
|
||||
self.set_cursor(self._selected_path, None, False)
|
||||
def scroll_to_selected(self):
|
||||
self.set_cursor(Gtk.TreePath(len(self._store)), None, False) # unset cursor
|
||||
self.save_scroll_to_cell(self._selected_path, None, True, 0.25)
|
||||
|
||||
def _on_row_activated(self, widget, path, view_column):
|
||||
if path != self._selected_path:
|
||||
if self._selected_path is not None:
|
||||
self._store[self._selected_path][1]=Pango.Weight.NORMAL
|
||||
self._store[path][1]=Pango.Weight.BOLD
|
||||
self._selected_path=path
|
||||
self.emit("item-selected")
|
||||
def _on_selection_changed(self, *args):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
if (path:=self._store.get_path(treeiter)) != self._selected_path:
|
||||
self._selected_path=path
|
||||
self.emit("item-selected")
|
||||
|
||||
class GenreList(SelectionList):
|
||||
def __init__(self, client):
|
||||
@@ -1880,9 +1889,6 @@ class GenreList(SelectionList):
|
||||
self._client.emitter.connect_after("reconnected", self._on_reconnected)
|
||||
self._client.emitter.connect("updated_db", self._refresh)
|
||||
|
||||
def deactivate(self):
|
||||
self.select_all()
|
||||
|
||||
def _refresh(self, *args):
|
||||
l=self._client.comp_list("genre")
|
||||
self.set_items(list(zip(l,l)))
|
||||
@@ -1938,14 +1944,13 @@ class ArtistList(SelectionList):
|
||||
self.select_path(Gtk.TreePath(1))
|
||||
else:
|
||||
self.select_path(Gtk.TreePath(0))
|
||||
self.scroll_to_selected()
|
||||
|
||||
def _on_button_press_event(self, widget, event):
|
||||
if (path_re:=widget.get_path_at_pos(int(event.x), int(event.y))) is not None:
|
||||
path=path_re[0]
|
||||
artist,genre=self.get_artist_at_path(path)
|
||||
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
||||
self._client.artist_to_playlist(artist, genre, "play")
|
||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
if event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._client.artist_to_playlist(artist, genre, "append")
|
||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||
self._artist_popover.open(artist, genre, self, event.x, event.y)
|
||||
@@ -1959,15 +1964,12 @@ class ArtistList(SelectionList):
|
||||
return self.get_artist_at_path(self.get_path_selected())
|
||||
|
||||
def add_to_playlist(self, mode):
|
||||
selected_rows=self._selection.get_selected_rows()
|
||||
if selected_rows is not None:
|
||||
path=selected_rows[1][0]
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
artist,genre=self.get_artist_at_path(path)
|
||||
self._client.artist_to_playlist(artist, genre, mode)
|
||||
|
||||
def show_info(self):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
path=self._store.get_path(treeiter)
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
artist,genre=self.get_artist_at_path(path)
|
||||
self._artist_popover.open(artist, genre, self, *self.get_popover_point(path))
|
||||
|
||||
@@ -2053,6 +2055,13 @@ class AlbumLoadingThread(threading.Thread):
|
||||
else:
|
||||
main_thread_function(self._store.set_sort_column_id)(6, Gtk.SortType.ASCENDING)
|
||||
idle_add(self._iconview.set_model, self._store)
|
||||
# select album
|
||||
path=main_thread_function(self._iconview.get_current_album_path)()
|
||||
if path is None:
|
||||
path=Gtk.TreePath(0)
|
||||
idle_add(self._iconview.set_cursor, path, None, False)
|
||||
idle_add(self._iconview.select_path, path)
|
||||
idle_add(self._iconview.scroll_to_path, path, True, 0.25, 0)
|
||||
# load covers
|
||||
total=2*len(self._store)
|
||||
@main_thread_function
|
||||
@@ -2102,7 +2111,7 @@ class AlbumLoadingThread(threading.Thread):
|
||||
|
||||
class AlbumList(Gtk.IconView):
|
||||
def __init__(self, client, settings, artist_list):
|
||||
super().__init__(item_width=0, pixbuf_column=0, markup_column=1, activate_on_single_click=True)
|
||||
super().__init__(item_width=0,pixbuf_column=0,markup_column=1,activate_on_single_click=True,selection_mode=Gtk.SelectionMode.BROWSE)
|
||||
self._settings=settings
|
||||
self._client=client
|
||||
self._artist_list=artist_list
|
||||
@@ -2148,19 +2157,24 @@ class AlbumList(Gtk.IconView):
|
||||
else:
|
||||
callback()
|
||||
|
||||
def scroll_to_current_album(self):
|
||||
def callback():
|
||||
song=self._client.currentsong()
|
||||
album=song["album"][0]
|
||||
self.unselect_all()
|
||||
def get_current_album_path(self):
|
||||
if (song:=self._client.currentsong()):
|
||||
album=[song["albumartist"][0], song["album"][0], song["date"][0]]
|
||||
row_num=len(self._store)
|
||||
for i in range(0, row_num):
|
||||
path=Gtk.TreePath(i)
|
||||
if self._store[path][4] == album:
|
||||
self.set_cursor(path, None, False)
|
||||
self.select_path(path)
|
||||
self.scroll_to_path(path, True, 0, 0)
|
||||
break
|
||||
if self._store[path][3:6] == album:
|
||||
return path
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
def scroll_to_current_album(self):
|
||||
def callback():
|
||||
if (path:=self.get_current_album_path()) is not None:
|
||||
self.set_cursor(path, None, False)
|
||||
self.select_path(path)
|
||||
self.scroll_to_path(path, True, 0.25, 0)
|
||||
if self._cover_thread.is_alive():
|
||||
self._cover_thread.set_callback(callback)
|
||||
else:
|
||||
@@ -2216,20 +2230,17 @@ class AlbumList(Gtk.IconView):
|
||||
self.set_sensitive(True)
|
||||
|
||||
def show_info(self):
|
||||
paths=self.get_selected_items()
|
||||
if len(paths) > 0:
|
||||
path=paths[0]
|
||||
if (path:=self.get_cursor()[1]) is not None:
|
||||
cell=self.get_cell_rect(path, None)[1]
|
||||
rect=self.get_allocation()
|
||||
x=max(min(rect.x+cell.width//2, rect.x+rect.width), rect.x)
|
||||
x=max(min(cell.x+cell.width//2, rect.x+rect.width), rect.x)
|
||||
y=max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y)
|
||||
tags=self._store[path][3:6]
|
||||
self._album_popover.open(*tags, self, x, y)
|
||||
|
||||
def add_to_playlist(self, mode):
|
||||
paths=self.get_selected_items()
|
||||
if len(paths) != 0:
|
||||
self._path_to_playlist(paths[0], mode)
|
||||
if (path:=self.get_cursor()[1]) is not None:
|
||||
self._path_to_playlist(path, mode)
|
||||
|
||||
def _on_cover_size_changed(self, *args):
|
||||
if self._client.connected():
|
||||
@@ -2264,25 +2275,23 @@ class Browser(Gtk.Paned):
|
||||
self.pack1(genre_window, False, False)
|
||||
self.pack2(self.paned1, True, False)
|
||||
|
||||
def back_to_current_album(self, force=False):
|
||||
if (song:=self._client.currentsong()):
|
||||
artist,genre=self._artist_list.get_artist_selected()
|
||||
# deactivate genre filter to show all artists (if needed)
|
||||
if song["genre"][0] != genre or force:
|
||||
self._genre_list.deactivate()
|
||||
# select artist
|
||||
if artist is None and not force: # all artists selected
|
||||
self._artist_list.highlight_selected()
|
||||
else: # one artist selected
|
||||
def back_to_current_album(self):
|
||||
song=self._client.currentsong()
|
||||
artist,genre=self._artist_list.get_artist_selected()
|
||||
if genre is None or song["genre"][0] == genre:
|
||||
if artist is not None and song["albumartist"][0] != artist:
|
||||
self._artist_list.select(song["albumartist"][0])
|
||||
self._album_list.scroll_to_current_album()
|
||||
self._artist_list.scroll_to_selected()
|
||||
else:
|
||||
self._album_list.scroll_to_current_album()
|
||||
else:
|
||||
self._genre_list.deactivate()
|
||||
self._genre_list.select_all()
|
||||
self._genre_list.scroll_to_selected()
|
||||
|
||||
def _on_genre_filter_changed(self, settings, key):
|
||||
if self._client.connected():
|
||||
if not settings.get_boolean(key):
|
||||
self._genre_list.deactivate()
|
||||
self._genre_list.select_all()
|
||||
|
||||
############
|
||||
# playlist #
|
||||
@@ -2483,21 +2492,24 @@ class PlaylistsPopover(Gtk.Popover):
|
||||
self._playlists_view.columns_autosize()
|
||||
|
||||
class PlaylistView(TreeView):
|
||||
selected_path=GObject.Property(type=Gtk.TreePath, default=None) # currently marked song (bold text)
|
||||
selected_path=GObject.Property(type=Gtk.TreePath, default=None) # currently marked song
|
||||
def __init__(self, client, settings):
|
||||
super().__init__(activate_on_single_click=True, reorderable=True, search_column=5, headers_visible=False)
|
||||
self._client=client
|
||||
self._settings=settings
|
||||
self._playlist_version=None
|
||||
self._inserted_path=None # needed for drag and drop
|
||||
|
||||
# selection
|
||||
self._selection=self.get_selection()
|
||||
self._selection.set_select_function(self._select_function)
|
||||
|
||||
# label
|
||||
self.label=Gtk.Label(xalign=0, ellipsize=Pango.EllipsizeMode.END)
|
||||
|
||||
# store
|
||||
# (track, title, human duration, file, duration, search, weight, weight_set)
|
||||
self._store=Gtk.ListStore(str, str, str, str, float, str, Pango.Weight, bool)
|
||||
# (track, title, human duration, file, duration, search)
|
||||
self._store=Gtk.ListStore(str, str, str, str, float, str)
|
||||
self.set_model(self._store)
|
||||
|
||||
# columns
|
||||
@@ -2507,10 +2519,9 @@ class PlaylistView(TreeView):
|
||||
renderer_text_ralign_tnum=Gtk.CellRendererText(xalign=1, attributes=attrs)
|
||||
renderer_text_centered_tnum=Gtk.CellRendererText(xalign=0.5, attributes=attrs)
|
||||
columns=(
|
||||
Gtk.TreeViewColumn(_("No"), renderer_text_centered_tnum, text=0, weight=6),
|
||||
# the order of weight_set and weight seems to be important here
|
||||
Gtk.TreeViewColumn(_("Title"), renderer_text, markup=1, weight_set=7, weight=6),
|
||||
Gtk.TreeViewColumn(_("Length"), renderer_text_ralign_tnum, text=2, weight=6)
|
||||
Gtk.TreeViewColumn(_("No"), renderer_text_centered_tnum, text=0),
|
||||
Gtk.TreeViewColumn(_("Title"), renderer_text, markup=1),
|
||||
Gtk.TreeViewColumn(_("Length"), renderer_text_ralign_tnum, text=2)
|
||||
)
|
||||
for column in columns:
|
||||
column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
||||
@@ -2548,35 +2559,31 @@ class PlaylistView(TreeView):
|
||||
def _select(self, path):
|
||||
self._unselect()
|
||||
try:
|
||||
self._store[path][6]=Pango.Weight.BOLD
|
||||
self._store[path][7]=True
|
||||
self.set_property("selected-path", path)
|
||||
self._selection.select_path(path)
|
||||
except IndexError: # invalid path
|
||||
pass
|
||||
|
||||
def _unselect(self):
|
||||
if self.get_property("selected-path") is not None:
|
||||
if (path:=self.get_property("selected-path")) is not None:
|
||||
self.set_property("selected-path", None)
|
||||
try:
|
||||
self._store[self.get_property("selected-path")][6]=Pango.Weight.NORMAL
|
||||
self._store[self.get_property("selected-path")][7]=False
|
||||
self.set_property("selected-path", None)
|
||||
self._selection.unselect_path(path)
|
||||
except IndexError: # invalid path
|
||||
self.set_property("selected-path", None)
|
||||
pass
|
||||
|
||||
def scroll_to_selected_title(self):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
path=self._store.get_path(treeiter)
|
||||
self.scroll_to_cell(path, None, True, 0.25)
|
||||
self.save_scroll_to_cell(path, None, True, 0.25)
|
||||
|
||||
def _refresh_selection(self): # Gtk.TreePath(len(self._store) is used to generate an invalid TreePath (needed to unset cursor)
|
||||
self.set_cursor(Gtk.TreePath(len(self._store)), None, False)
|
||||
song=self._client.status().get("song")
|
||||
if song is None:
|
||||
self._selection.unselect_all()
|
||||
self._unselect()
|
||||
else:
|
||||
path=Gtk.TreePath(int(song))
|
||||
self._selection.select_path(path)
|
||||
self._select(path)
|
||||
|
||||
def _set_playlist_info(self, text):
|
||||
@@ -2593,9 +2600,9 @@ class PlaylistView(TreeView):
|
||||
|
||||
def _on_key_release_event(self, widget, event):
|
||||
if event.keyval == Gdk.keyval_from_name("Delete"):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
try:
|
||||
self._store.remove(treeiter)
|
||||
self._store.remove(self._store.get_iter(path))
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -2642,23 +2649,21 @@ class PlaylistView(TreeView):
|
||||
title=song.get_markup()
|
||||
try:
|
||||
treeiter=self._store.get_iter(song["pos"])
|
||||
except ValueError:
|
||||
self._store.insert_with_valuesv(-1, range(8), [
|
||||
song["track"][0], title,
|
||||
str(song["duration"]), song["file"],
|
||||
float(song["duration"]), song["title"][0]
|
||||
])
|
||||
else:
|
||||
self._store.set(treeiter,
|
||||
0, song["track"][0],
|
||||
1, title,
|
||||
2, str(song["duration"]),
|
||||
3, song["file"],
|
||||
4, float(song["duration"]),
|
||||
5, song["title"][0],
|
||||
6, Pango.Weight.NORMAL,
|
||||
7, False
|
||||
5, song["title"][0]
|
||||
)
|
||||
except:
|
||||
self._store.insert_with_valuesv(-1, range(8), [
|
||||
song["track"][0], title,
|
||||
str(song["duration"]), song["file"],
|
||||
float(song["duration"]), song["title"][0],
|
||||
Pango.Weight.NORMAL, False
|
||||
])
|
||||
self.thaw_child_notify()
|
||||
for i in reversed(range(int(self._client.status()["playlistlength"]), len(self._store))):
|
||||
treeiter=self._store.get_iter(i)
|
||||
@@ -2689,22 +2694,22 @@ class PlaylistView(TreeView):
|
||||
def _on_reconnected(self, *args):
|
||||
self.set_sensitive(True)
|
||||
|
||||
def _select_function(self, selection, model, path, path_currently_selected):
|
||||
return (path == self.get_property("selected-path")) == (not path_currently_selected)
|
||||
|
||||
def show_info(self):
|
||||
if (treeiter:=self._selection.get_selected()[1]) is not None:
|
||||
path=self._store.get_path(treeiter)
|
||||
if (path:=self.get_cursor()[0]) is not None:
|
||||
self._song_popover.open(self._store[path][3], self, *self.get_popover_point(path))
|
||||
|
||||
class PlaylistWindow(Gtk.Overlay):
|
||||
def __init__(self, client, settings):
|
||||
super().__init__()
|
||||
self._back_to_current_song_button=Gtk.Button(
|
||||
image=Gtk.Image.new_from_icon_name("go-previous-symbolic", Gtk.IconSize.BUTTON), tooltip_text=_("Scroll to current song"),
|
||||
can_focus=False
|
||||
)
|
||||
self._back_button_icon=Gtk.Image.new_from_icon_name("go-down-symbolic", Gtk.IconSize.BUTTON)
|
||||
self._back_to_current_song_button=Gtk.Button(image=self._back_button_icon, tooltip_text=_("Scroll to current song"), can_focus=False)
|
||||
self._back_to_current_song_button.get_style_context().add_class("osd")
|
||||
self._back_button_revealer=Gtk.Revealer(
|
||||
child=self._back_to_current_song_button, transition_duration=0,
|
||||
margin_bottom=6, margin_start=6, halign=Gtk.Align.START, valign=Gtk.Align.END
|
||||
margin_bottom=6, margin_top=6, halign=Gtk.Align.CENTER, valign=Gtk.Align.END
|
||||
)
|
||||
self._treeview=PlaylistView(client, settings)
|
||||
scroll=Gtk.ScrolledWindow(child=self._treeview)
|
||||
@@ -2728,14 +2733,23 @@ class PlaylistWindow(Gtk.Overlay):
|
||||
if visible_range is None or self._treeview.get_property("selected-path") is None:
|
||||
self._back_button_revealer.set_reveal_child(False)
|
||||
else:
|
||||
current_song_visible=(visible_range[0] <= self._treeview.get_property("selected-path") <= visible_range[1])
|
||||
self._back_button_revealer.set_reveal_child(not(current_song_visible))
|
||||
if visible_range[0] > self._treeview.get_property("selected-path"): # current song is above upper edge
|
||||
self._back_button_icon.set_property("icon-name", "go-up-symbolic")
|
||||
self._back_button_revealer.set_valign(Gtk.Align.START)
|
||||
self._back_button_revealer.set_reveal_child(True)
|
||||
elif self._treeview.get_property("selected-path") > visible_range[1]: # current song is below lower edge
|
||||
self._back_button_icon.set_property("icon-name", "go-down-symbolic")
|
||||
self._back_button_revealer.set_valign(Gtk.Align.END)
|
||||
self._back_button_revealer.set_reveal_child(True)
|
||||
else: # current song is visible
|
||||
self._back_button_revealer.set_reveal_child(False)
|
||||
|
||||
def _on_back_to_current_song_button_clicked(self, *args):
|
||||
self._treeview.set_cursor(Gtk.TreePath(len(self._treeview.get_model())), None, False) # unset cursor
|
||||
if self._treeview.get_property("selected-path") is not None:
|
||||
self._treeview.get_selection().select_path(self._treeview.get_property("selected-path"))
|
||||
self._treeview.scroll_to_selected_title()
|
||||
self._back_button_revealer.set_reveal_child(False) # workaround for Gtk bug in _on_show_hide_back_button
|
||||
|
||||
####################
|
||||
# cover and lyrics #
|
||||
@@ -2872,30 +2886,30 @@ class CoverEventBox(Gtk.EventBox):
|
||||
def _on_disconnected(self, *args):
|
||||
self._album_popover.popdown()
|
||||
|
||||
class MainCover(Gtk.Image):
|
||||
class MainCover(Gtk.DrawingArea):
|
||||
def __init__(self, client, settings):
|
||||
super().__init__()
|
||||
self._client=client
|
||||
self._settings=settings
|
||||
# set default size
|
||||
size=self._settings.get_int("track-cover")
|
||||
self.set_size_request(size, size)
|
||||
self._fallback=True
|
||||
|
||||
# connect
|
||||
self._client.emitter.connect("current_song", self._refresh)
|
||||
self._client.emitter.connect("disconnected", self._on_disconnected)
|
||||
self._client.emitter.connect("reconnected", self._on_reconnected)
|
||||
self._settings.connect("changed::track-cover", self._on_settings_changed)
|
||||
|
||||
def _clear(self):
|
||||
size=self._settings.get_int("track-cover")
|
||||
self.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size))
|
||||
self._fallback=True
|
||||
self.queue_draw()
|
||||
|
||||
def _refresh(self, *args):
|
||||
if self._client.current_cover is None:
|
||||
self._clear()
|
||||
else:
|
||||
self.set_from_pixbuf(self._client.current_cover.get_pixbuf(self._settings.get_int("track-cover")))
|
||||
self._pixbuf=self._client.current_cover.get_pixbuf()
|
||||
self._surface=Gdk.cairo_surface_create_from_pixbuf(self._pixbuf, 0, None)
|
||||
self._fallback=False
|
||||
self.queue_draw()
|
||||
|
||||
def _on_disconnected(self, *args):
|
||||
self.set_sensitive(False)
|
||||
@@ -2904,10 +2918,19 @@ class MainCover(Gtk.Image):
|
||||
def _on_reconnected(self, *args):
|
||||
self.set_sensitive(True)
|
||||
|
||||
def _on_settings_changed(self, *args):
|
||||
size=self._settings.get_int("track-cover")
|
||||
self.set_size_request(size, size)
|
||||
self._refresh()
|
||||
def do_draw(self, context):
|
||||
if self._fallback:
|
||||
size=min(self.get_allocated_height(), self.get_allocated_width())
|
||||
self._pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, size, size)
|
||||
self._surface=Gdk.cairo_surface_create_from_pixbuf(self._pixbuf, 0, None)
|
||||
scale_factor=1
|
||||
else:
|
||||
scale_factor=min(self.get_allocated_width()/self._pixbuf.get_width(), self.get_allocated_height()/self._pixbuf.get_height())
|
||||
context.scale(scale_factor, scale_factor)
|
||||
x=((self.get_allocated_width()/scale_factor)-self._pixbuf.get_width())/2
|
||||
y=((self.get_allocated_height()/scale_factor)-self._pixbuf.get_height())/2
|
||||
context.set_source_surface(self._surface, x, y)
|
||||
context.paint()
|
||||
|
||||
class CoverLyricsWindow(Gtk.Overlay):
|
||||
def __init__(self, client, settings):
|
||||
@@ -2946,7 +2969,7 @@ class CoverLyricsWindow(Gtk.Overlay):
|
||||
self._client.emitter.connect("reconnected", self._on_reconnected)
|
||||
|
||||
# packing
|
||||
self.add(main_cover)
|
||||
self.add(Gtk.AspectFrame(child=main_cover, shadow_type=Gtk.ShadowType.NONE))
|
||||
self.add_overlay(self._stack)
|
||||
self.add_overlay(self._lyrics_button_revealer)
|
||||
|
||||
@@ -3483,9 +3506,6 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
def __init__(self, client, settings, **kwargs):
|
||||
super().__init__(title=("mpdevil"), icon_name="org.mpdevil.mpdevil", **kwargs)
|
||||
self.set_default_icon_name("org.mpdevil.mpdevil")
|
||||
self.set_default_size(settings.get_int("width"), settings.get_int("height"))
|
||||
if settings.get_boolean("maximize"):
|
||||
self.maximize() # request maximize
|
||||
self._client=client
|
||||
self._settings=settings
|
||||
self._use_csd=self._settings.get_boolean("use-csd")
|
||||
@@ -3536,9 +3556,10 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
self._search_button=Gtk.ToggleButton(
|
||||
image=icon("system-search-symbolic"), tooltip_text=_("Search"), can_focus=False, no_show_all=True)
|
||||
self._settings.bind("mini-player", self._search_button, "visible", Gio.SettingsBindFlags.INVERT_BOOLEAN|Gio.SettingsBindFlags.GET)
|
||||
self._back_button=Gtk.Button(
|
||||
image=icon("go-previous-symbolic"), tooltip_text=_("Back to current album"), can_focus=False, no_show_all=True)
|
||||
self._settings.bind("mini-player", self._back_button, "visible", Gio.SettingsBindFlags.INVERT_BOOLEAN|Gio.SettingsBindFlags.GET)
|
||||
back_button=Gtk.Button(
|
||||
image=icon("go-previous-symbolic"), tooltip_text=_("Back to current album"),
|
||||
action_name="win.back-to-current-album", can_focus=False, no_show_all=True)
|
||||
self._settings.bind("mini-player", back_button, "visible", Gio.SettingsBindFlags.INVERT_BOOLEAN|Gio.SettingsBindFlags.GET)
|
||||
|
||||
# stack
|
||||
self._stack=Gtk.Stack(transition_type=Gtk.StackTransitionType.CROSSFADE)
|
||||
@@ -3579,8 +3600,6 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
|
||||
# connect
|
||||
self._search_button.connect("toggled", self._on_search_button_toggled)
|
||||
self._back_button.connect("clicked", self._on_back_button_clicked)
|
||||
self._back_button.connect("button-press-event", self._on_back_button_press_event)
|
||||
self._settings.connect_after("changed::mini-player", self._mini_player)
|
||||
self._settings.connect_after("notify::cursor-watch", self._on_cursor_watch)
|
||||
self._settings.connect("changed::playlist-right", self._on_playlist_pos_changed)
|
||||
@@ -3601,11 +3620,11 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
if self._use_csd:
|
||||
self._header_bar=Gtk.HeaderBar(show_close_button=True)
|
||||
self.set_titlebar(self._header_bar)
|
||||
self._header_bar.pack_start(self._back_button)
|
||||
self._header_bar.pack_start(back_button)
|
||||
self._header_bar.pack_end(self._menu_button)
|
||||
self._header_bar.pack_end(self._search_button)
|
||||
else:
|
||||
action_bar.pack_start(self._back_button)
|
||||
action_bar.pack_start(back_button)
|
||||
action_bar.pack_end(self._menu_button)
|
||||
action_bar.pack_end(self._search_button)
|
||||
action_bar.pack_start(playback_control)
|
||||
@@ -3621,22 +3640,27 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
overlay.add_overlay(update_notify)
|
||||
overlay.add_overlay(connection_notify)
|
||||
self.add(overlay)
|
||||
|
||||
# bring player in consistent state
|
||||
self._client.emitter.emit("disconnected")
|
||||
self._mini_player()
|
||||
# indicate connection process in window title
|
||||
if self._use_csd:
|
||||
self._header_bar.set_subtitle(_("connecting…"))
|
||||
else:
|
||||
self.set_title("mpdevil "+_("connecting…"))
|
||||
# set default window size
|
||||
if self._settings.get_boolean("mini-player"):
|
||||
self.set_default_size(settings.get_int("mini-player-width"), settings.get_int("mini-player-height"))
|
||||
else:
|
||||
self.set_default_size(settings.get_int("width"), settings.get_int("height"))
|
||||
if settings.get_boolean("maximize"):
|
||||
self.maximize() # request maximize
|
||||
# show window
|
||||
self.show_all()
|
||||
while Gtk.events_pending(): # ensure window is visible
|
||||
Gtk.main_iteration_do(True)
|
||||
# restore paned settings when window is visible (fixes a bug when window is maximized)
|
||||
self._settings.bind("paned0", self._paned0, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned1", self._browser.paned1, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned2", self._paned2, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned3", self._browser, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
if not self._settings.get_boolean("mini-player"):
|
||||
self._bind_paned_settings() # restore paned settings when window is visible (fixes a bug when window is maximized)
|
||||
|
||||
# start client
|
||||
def callback(*args):
|
||||
@@ -3644,20 +3668,42 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
return False
|
||||
idle_add(callback)
|
||||
|
||||
def _clear_title(self):
|
||||
self.set_title("mpdevil")
|
||||
if self._use_csd:
|
||||
self._header_bar.set_subtitle("")
|
||||
|
||||
def _bind_paned_settings(self):
|
||||
self._settings.bind("paned0", self._paned0, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned1", self._browser.paned1, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned2", self._paned2, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
self._settings.bind("paned3", self._browser, "position", Gio.SettingsBindFlags.DEFAULT)
|
||||
|
||||
def _unbind_paned_settings(self):
|
||||
self._settings.unbind(self._paned0, "position")
|
||||
self._settings.unbind(self._browser.paned1, "position")
|
||||
self._settings.unbind(self._paned2, "position")
|
||||
self._settings.unbind(self._browser, "position")
|
||||
|
||||
def _mini_player(self, *args):
|
||||
if self.is_maximized():
|
||||
self.unmaximize()
|
||||
if self._settings.get_boolean("mini-player"):
|
||||
if self.is_maximized():
|
||||
self.unmaximize()
|
||||
self.resize(1,1)
|
||||
self._unbind_paned_settings()
|
||||
self.resize(self._settings.get_int("mini-player-width"), self._settings.get_int("mini-player-height"))
|
||||
else:
|
||||
self.resize(self._settings.get_int("width"), self._settings.get_int("height"))
|
||||
self.show_all()
|
||||
while Gtk.events_pending(): # ensure window is resized
|
||||
Gtk.main_iteration_do(True)
|
||||
self._bind_paned_settings()
|
||||
self.show_all() # show hidden gui elements
|
||||
|
||||
def _on_toggle_lyrics(self, action, param):
|
||||
self._cover_lyrics_window.lyrics_button.emit("clicked")
|
||||
|
||||
def _on_back_to_current_album(self, action, param):
|
||||
self._back_button.emit("clicked")
|
||||
self._search_button.set_active(False)
|
||||
self._browser.back_to_current_album()
|
||||
|
||||
def _on_toggle_search(self, action, param):
|
||||
self._search_button.emit("clicked")
|
||||
@@ -3707,16 +3753,9 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
else:
|
||||
self._stack.set_visible_child_name("browser")
|
||||
|
||||
def _on_back_button_clicked(self, *args):
|
||||
self._search_button.set_active(False)
|
||||
self._browser.back_to_current_album()
|
||||
|
||||
def _on_back_button_press_event(self, widget, event):
|
||||
if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
||||
self._browser.back_to_current_album(force=True)
|
||||
|
||||
def _on_song_changed(self, *args):
|
||||
if (song:=self._client.currentsong()):
|
||||
self.lookup_action("back-to-current-album").set_enabled(True)
|
||||
album=song.get_album_with_date()
|
||||
title=" • ".join(filter(None, (song["title"][0], str(song["artist"]))))
|
||||
if self._use_csd:
|
||||
@@ -3737,32 +3776,32 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
else:
|
||||
self.get_application().withdraw_notification("title-change")
|
||||
else:
|
||||
self.set_title("mpdevil")
|
||||
if self._use_csd:
|
||||
self._header_bar.set_subtitle("")
|
||||
self.lookup_action("back-to-current-album").set_enabled(False)
|
||||
self._clear_title()
|
||||
self.get_application().withdraw_notification("title-change")
|
||||
|
||||
def _on_reconnected(self, *args):
|
||||
for action in ("stats","toggle-lyrics","back-to-current-album","toggle-search"):
|
||||
self._clear_title()
|
||||
for action in ("stats","toggle-lyrics","toggle-search"):
|
||||
self.lookup_action(action).set_enabled(True)
|
||||
self._search_button.set_sensitive(True)
|
||||
self._back_button.set_sensitive(True)
|
||||
|
||||
def _on_disconnected(self, *args):
|
||||
self.set_title("mpdevil")
|
||||
if self._use_csd:
|
||||
self._header_bar.set_subtitle("")
|
||||
self._clear_title()
|
||||
for action in ("stats","toggle-lyrics","back-to-current-album","toggle-search"):
|
||||
self.lookup_action(action).set_enabled(False)
|
||||
self._search_button.set_active(False)
|
||||
self._search_button.set_sensitive(False)
|
||||
self._back_button.set_sensitive(False)
|
||||
|
||||
def _on_size_allocate(self, widget, rect):
|
||||
if not self.is_maximized() and not self._settings.get_boolean("mini-player"):
|
||||
if not self.is_maximized():
|
||||
if (size:=self.get_size()) != self._size: # prevent unneeded write operations
|
||||
self._settings.set_int("width", size[0])
|
||||
self._settings.set_int("height", size[1])
|
||||
if self._settings.get_boolean("mini-player"):
|
||||
self._settings.set_int("mini-player-width", size[0])
|
||||
self._settings.set_int("mini-player-height", size[1])
|
||||
else:
|
||||
self._settings.set_int("width", size[0])
|
||||
self._settings.set_int("height", size[1])
|
||||
self._size=size
|
||||
|
||||
def _on_cursor_watch(self, obj, typestring):
|
||||
@@ -3825,7 +3864,10 @@ class mpdevil(Gtk.Application):
|
||||
Gtk.binding_entry_remove(Gtk.binding_set_find('GtkTreeView'), Gdk.keyval_from_name("space"), Gdk.ModifierType.MOD2_MASK)
|
||||
|
||||
def do_activate(self):
|
||||
self._window.present()
|
||||
try:
|
||||
self._window.present()
|
||||
except: # failed to show window so the user can't see anything
|
||||
self.quit()
|
||||
|
||||
def do_shutdown(self):
|
||||
Gtk.Application.do_shutdown(self)
|
||||
@@ -3855,4 +3897,5 @@ class mpdevil(Gtk.Application):
|
||||
|
||||
if __name__ == "__main__":
|
||||
app=mpdevil()
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL) # allow using ctrl-c to terminate
|
||||
app.run(sys.argv)
|
||||
|
Reference in New Issue
Block a user