Initial commit

This commit is contained in:
Raoul Snyman 2021-06-15 15:09:41 -07:00
parent 35b750dd77
commit f66fea1e90
Signed by: raoul
GPG Key ID: F55BCED79626AE9C
27 changed files with 1164 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
*.py[cd]

194
mainwindow.py Normal file
View File

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
from time import sleep
from funksnake import Funkwhale
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
from ui_mainwindow import UiMainWindow
from settingsdialog import SettingsDialog
class ThreadWorker(QtCore.QObject):
"""
The :class:`~threads.ThreadWorker` class provides a base class for thread workers.
In Qt5, all work is done in workers, rather than actual QThread classes, and this case class provides
some utility signals and slots to make it easier to use threads and thread workers.
"""
quit = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(str, str)
def start(self):
"""
The start method is how the worker runs. Basically, put your code here.
"""
raise NotImplementedError('Your class needs to override this method and run self.quit.emit() when complete')
class AlbumArtWorker(ThreadWorker):
album_item = None
artwork_url = None
def start(self):
if not self.album_item:
self.error.emit('No album QListWidgetItem set', 'The album_item attribute was not set')
self.quit.emit()
return
if not self.artwork_url:
self.error.emit('No artwork URL set', 'The artwork_url attribute was not set')
self.quit.emit()
return
reply = window.network_manager.get(QtNetwork.QNetworkRequest(QtCore.QUrl(self.artwork_url)))
while not reply.isFinished():
print(reply.error())
sleep(0.1)
print(reply)
if reply.error() == QtNetwork.QNetworkReply.NoError:
jpeg_data = reply.readAll()
pixmap = QtGui.QPixmap()
pixmap.loadFromData(jpeg_data)
self.albumListWidget.addItem(QtWidgets.QListWidgetItem(QtGui.QIcon(pixmap), album['title']))
else:
self.albumListWidget.addItem(QtWidgets.QListWidgetItem(album['title']))
class AlbumWorker(ThreadWorker):
"""
A thread worker to fetch the album art
"""
window = None
def start(self):
if not self.window:
self.error.emit('No window set', 'The window attribute was not set')
self.quit.emit()
return
self.window.albumListWidget.clear()
for album in self.window.funkwhale.albums.list()['results']:
# TODO: Cache this
album_item = QtWidgets.QListWidgetItem(album['title'])
self.window.albumListWidget.addItem(album_item)
artwork_worker = AlbumArtWorker()
artwork_worker.album_item = album_item
artwork_worker.artwork_url = album['cover']['urls']['medium_square_crop']
self.window.run_thread(artwork_worker, 'album-{}'.format(album['title']))
self.quit.emit()
class MainWindow(QtWidgets.QMainWindow, UiMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
self.settingsAction.triggered.connect(self.on_settings_action_triggered)
self.settings = QtCore.QSettings('info.snyman.pyfunkwhale', 'PyFunkwhale')
self.network_manager = QtNetwork.QNetworkAccessManager()
self.settings_dialog = SettingsDialog(self)
self.funkwhale = None
self.threads = {}
if self.settings.contains('funkwhale/server_url'):
self.setup_funkwhale(self.settings.value('funkwhale/server_url'),
self.settings.value('funkwhale/username', None),
self.settings.value('funkwhale/password', None))
self.load_albums()
self.load_artists()
def run_thread(self, worker, thread_name):
"""
Create a thread and its worker. This gets rid of a lot of unnecessary boilerplate code.
:param ThreadWorker worker: A worker object that actually performs the work
:param str thread_name: The name of the thread, used to keep track of the thread.
:param bool can_start: Start the thread. Defaults to True.
"""
if not thread_name:
raise ValueError('A thread name is required')
if thread_name in self.threads:
raise KeyError('A thread with name "{}" already exists'.format(thread_name))
# Create the thread and add the thread and the worker to the thread tracker
thread = QtCore.QThread()
self.threads[thread_name] = {
'thread': thread,
'worker': worker
}
# Move the worker into the thread's context
worker.moveToThread(thread)
# Connect slots and signals
thread.started.connect(worker.start)
worker.quit.connect(thread.quit)
worker.quit.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.finished.connect(self.make_remove_thread(thread_name))
thread.start()
def get_thread_worker(self, thread_name):
"""
Get the worker by the thread name
:param str thread_name: The name of the thread
:returns: The worker for this thread name, or None
"""
thread = self.threads.get(thread_name)
if not thread:
return
return thread.get('worker')
def is_thread_finished(self, thread_name):
"""
Check if a thread is finished running.
:param str thread_name: The name of the thread
:returns: True if the thread is finished, False if it is still running
"""
return thread_name not in self.threads or self.threads[thread_name]['thread'].isFinished()
def make_remove_thread(self, thread_name):
"""
Create a function to remove the thread once the thread is finished.
:param str thread_name: The name of the thread which should be removed from the thread registry.
:returns: A function which will remove the thread from the thread registry.
"""
def remove_thread():
"""
Stop and remove a registered thread
:param str thread_name: The name of the thread to stop and remove
"""
if thread_name in self.threads:
del self.threads[thread_name]
return remove_thread
def setup_funkwhale(self, server_url=None, username=None, password=None):
if not self.funkwhale and not server_url:
raise ValueError('A server url needs to be defined')
if not self.funkwhale and server_url:
self.funkwhale = Funkwhale(server_url)
if username and password:
self.funkwhale.login(username, password)
def load_albums(self):
if not self.funkwhale or not self.funkwhale.token:
return
def load_artists(self):
if not self.funkwhale or not self.funkwhale.token:
return
self.artistListWidget.clear()
for artist in self.funkwhale.artists.list()['results']:
self.artistListWidget.addItem(QtWidgets.QListWidgetItem(artist['name']))
def on_settings_action_triggered(self):
self.settings_dialog.server_url = self.settings.value('funkwhale/server_url', '')
self.settings_dialog.username = self.settings.value('funkwhale/username', '')
self.settings_dialog.password = self.settings.value('funkwhale/password', '')
if self.settings_dialog.exec() == QtWidgets.QDialog.Accepted:
self.settings.setValue('funkwhale/server_url', self.settings_dialog.server_url)
self.settings.setValue('funkwhale/username', self.settings_dialog.username)
self.settings.setValue('funkwhale/password', self.settings_dialog.password)
self.setup_funkwhale(self.settings.value('funkwhale/server_url'),
self.settings.value('funkwhale/username', None),
self.settings.value('funkwhale/password', None))

329
mainwindow.ui Normal file
View File

@ -0,0 +1,329 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>719</width>
<height>574</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="centralLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="playbackWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>36</height>
</size>
</property>
<layout class="QHBoxLayout" name="playbackLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="toggleButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="expand"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="previousButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="media-skip-backward"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="playButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="media-playback-start"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="nextButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="media-skip-forward"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="positionLabel">
<property name="text">
<string>01:43</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="positionSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="shuffleButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="media-playlist-normal"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="repeatButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="media-repeat-none"/>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="muteButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="player-volume"/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="volumeSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="menuButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset theme="menu_new"/>
</property>
<property name="popupMode">
<enum>QToolButton::DelayedPopup</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>1</number>
</property>
<widget class="QListWidget" name="viewListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="albumPage">
<layout class="QVBoxLayout" name="albumPageLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="albumListWidget"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="artistPage">
<layout class="QVBoxLayout" name="artistPageLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="artistListWidget"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="trackPage">
<layout class="QVBoxLayout" name="trackPageLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="trackListWidget"/>
</item>
</layout>
</widget>
</widget>
<widget class="QListWidget" name="playlistListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>719</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionPlay">
<property name="text">
<string>&amp;Play</string>
</property>
<property name="toolTip">
<string>Play</string>
</property>
</action>
</widget>
<resources>
<include location="resources/pyfunkwhale.qrc"/>
</resources>
<connections/>
</ui>

14
pyfunkwhale.py Normal file
View File

@ -0,0 +1,14 @@
from PyQt5 import QtWidgets
from mainwindow import MainWindow
def main():
"""Main"""
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
return app.exec()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 3 L 2 19 L 3 19 L 20 19 L 20 3 L 3 3 L 2 3 z M 3 4 L 4 4 L 4 18 L 3 18 L 3 4 z M 5 4 L 6 4 A 1 1 0 0 0 7 5 A 1 1 0 0 0 8 4 L 12 4 L 16 4 A 1 1 0 0 0 17 5 A 1 1 0 0 0 18 4 L 19 4 L 19 11 L 19 18 L 18 18 A 1 1 0 0 0 17 17 A 1 1 0 0 0 16 18 L 12 18 L 8 18 A 1 1 0 0 0 7 17 A 1 1 0 0 0 6 18 L 5 18 L 5 11 L 5 4 z M 5 11 A 7 7 0 0 0 12 18 A 7 7 0 0 0 19 11 A 7 7 0 0 0 12 4 A 7 7 0 0 0 5 11 z M 12 5 A 6 6 0 0 1 18 11 A 6 6 0 0 1 12 17 A 6 6 0 0 1 6 11 A 6 6 0 0 1 12 5 z M 12 10 A 1 1 0 0 0 11 11 A 1 1 0 0 0 12 12 A 1 1 0 0 0 13 11 A 1 1 0 0 0 12 10 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 895 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 3 L 2 19 L 3 19 L 11 19 L 11 18 L 8 18 A 1 1 0 0 0 7 17 A 1 1 0 0 0 6 18 L 5 18 L 5 11 L 5 4 L 6 4 A 1 1 0 0 0 7 5 A 1 1 0 0 0 8 4 L 12 4 L 16 4 A 1 1 0 0 0 17 5 A 1 1 0 0 0 18 4 L 19 4 L 19 7 L 20 7 L 20 3 L 3 3 L 2 3 z M 12 4 A 7 7 0 0 0 5 11 A 7 7 0 0 0 11 17.921875 L 11 16.910156 A 6 6 0 0 1 6 11 A 6 6 0 0 1 12 5 A 6 6 0 0 1 16.462891 7 L 17.742188 7 A 7 7 0 0 0 12 4 z M 3 4 L 4 4 L 4 18 L 3 18 L 3 4 z M 16 8 L 16 9 L 16 14.5 C 15.5818 14.1852 15.066 14 14.5 14 C 13.115 14 12 15.115 12 16.5 C 12 17.885 13.115 19 14.5 19 C 15.885 19 17 17.885 17 16.5 L 17 10 L 19 10 C 19 8.892 18.108 8 17 8 L 16 8 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 956 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m3 3v16h6v-16zm10 0v16h6v-16z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m3 3v16l16-8z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 275 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m3 3h16v16h-16z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 4.5 3 C 3.6715787 3 3.0000083 3.6715704 3 4.5 C 3.0000083 5.3284296 3.6715787 6 4.5 6 C 5.3284213 6 5.9999917 5.3284296 6 4.5 C 5.9999917 3.6715704 5.3284213 3 4.5 3 z M 7 4 L 7 5 L 8 5 L 8 4 L 7 4 z M 9 4 L 9 5 L 19 5 L 19 4 L 9 4 z M 9 6 L 9 7 L 12 7 L 12 6 L 9 6 z M 4.5 9 C 3.6715732 8.9999988 3 9.6715721 3 10.5 C 3 11.328428 3.6715732 12.000001 4.5 12 C 5.3284268 12.000001 6 11.328428 6 10.5 C 6 9.6715721 5.3284268 8.9999988 4.5 9 z M 7 10 L 7 11 L 8 11 L 8 10 L 7 10 z M 9 10 L 9 11 L 19 11 L 19 10 L 9 10 z M 15 12 L 15 15 L 12 15 L 12 16 L 15 16 L 15 19 L 16 19 L 16 16 L 19 16 L 19 15 L 16 15 L 16 12 L 15 12 z M 4.5 15 C 3.6715732 14.999999 3 15.671572 3 16.5 C 3 17.328428 3.6715732 18.000001 4.5 18 C 5.3284268 18.000001 6 17.328428 6 16.5 C 6 15.671572 5.3284268 14.999999 4.5 15 z M 7 16 L 7 17 L 8 17 L 8 16 L 7 16 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 16 5 L 16 7 L 3 7 L 3 8 L 16 8 L 16 10 L 19 7.5 L 16 5 z M 16 12 L 16 14 L 3 14 L 3 15 L 16 15 L 16 17 L 19 14.5 L 16 12 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 465 B

View File

@ -0,0 +1,10 @@
<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path class="ColorScheme-Text" d="m16 5v2h-10c-1.662 0-3 1.338-3 3v1h1v-1c0-1.108 0.892-2 2-2h10v2l3-2.5zm2 6v1c0 1.108-0.892 2-2 2h-10v-2l-3 2.5 3 2.5v-2h10c1.662 0 3-1.338 3-3v-1z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 16 5 L 16 7 L 13 7 C 10.784 7 9 8.784 9 11 C 9 12.662 7.662 14 6 14 L 3 14 L 3 15 L 6 15 C 8.216 15 10 13.216 10 11 C 10 9.338 11.338 8 13 8 L 16 8 L 16 10 L 19 7.5 L 16 5 z M 3 7 L 3 8 L 6 8 C 6.8536128 8 7.6168144 8.3572581 8.1621094 8.9257812 C 8.2578781 8.5814037 8.3956215 8.2568349 8.5742188 7.9570312 C 7.8779048 7.3680394 6.9875937 7 6 7 L 3 7 z M 16 12 L 16 14 L 13 14 C 12.146387 14 11.383186 13.642742 10.837891 13.074219 C 10.742121 13.418597 10.604379 13.743165 10.425781 14.042969 C 11.122095 14.631961 12.012406 15 13 15 L 16 15 L 16 17 L 19 14.5 L 16 12 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 914 B

View File

@ -0,0 +1,12 @@
<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
.ColorScheme-NegativeText {
color:#da4453;
}
</style>
<path d="m16 5v2h-10c-1.662 0-3 1.338-3 3v1h1v-1c0-1.108.892-2 2-2h10v2l3-2.5zm-3 9h-7v-2l-3 2.5 3 2.5v-2h7z" class="ColorScheme-Text" fill="currentColor"/>
<path d="m14.5 11.79296875-.70703125.70703125 2 2-2 2 .70703125.70703125 2-2 2 2 .70703125-.70703125-2-2 2-2-.70703125-.70703125-2 2z" class="ColorScheme-NegativeText" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 628 B

View File

@ -0,0 +1,12 @@
<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<g class="ColorScheme-Text" fill="currentColor">
<path d="m6 12-3 2.5 3 2.5v-2h9v-1h-9z"/>
<path d="m16 5v2h-10c-1.662 0-3 1.338-3 3v1h1v-1c0-1.108.892-2 2-2h10v2l3-2.5z"/>
<path d="m17.29296875 11-1.5 1.5.70703125.70703125.5-.5v3.29296875h-1v1h3v-1h-1v-5z" fill-rule="evenodd"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" id="svg6">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m11 3-10 8 10 8v-8zm0 8 10 8v-16z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" id="svg6">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m1 3v16l10-8zm10 8v8l10-8-10-8z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" id="svg6">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m0 3v16h2v-16zm2 8 10 8v-16zm10 0 10 8v-16z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" id="svg6">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<path d="m0 3v16l10-8zm10 8v8l10-8-10-8zm10 0v8h2v-16h-2z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 320 B

14
resources/menu_new.svg Normal file
View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m3 5v2h16v-2h-16m0 5v2h16v-2h-16m0 5v2h16v-2h-16"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 397 B

23
resources/pyfunkwhale.qrc Normal file
View File

@ -0,0 +1,23 @@
<RCC>
<qresource prefix="playback">
<file>media-playback-start.svg</file>
<file>media-playback-pause.svg</file>
<file>media-playback-stop.svg</file>
<file>media-playlist-normal.svg</file>
<file>media-playlist-repeat.svg</file>
<file>media-playlist-shuffle.svg</file>
<file>media-repeat-none.svg</file>
<file>media-repeat-single.svg</file>
<file>media-seek-backward.svg</file>
<file>media-seek-forward.svg</file>
<file>media-skip-backward.svg</file>
<file>media-skip-forward.svg</file>
<file>media-album-track.svg</file>
<file>media-album-cover-manager-amarok.svg</file>
<file>media-playlist-append.svg</file>
</qresource>
<qresource prefix="general">
<file>menu_new.svg</file>
<file>speaker.svg</file>
</qresource>
</RCC>

13
resources/speaker.svg Normal file
View File

@ -0,0 +1,13 @@
<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<style id="current-color-scheme" type="text/css">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
<g class="ColorScheme-Text" fill="currentColor">
<path d="m14.324219 7.28125-.539063.8613281a4 4 0 0 1 1.214844 2.8574219 4 4 0 0 1 -1.210938 2.861328l.539063.863281a5 5 0 0 0 1.671875-3.724609 5 5 0 0 0 -1.675781-3.71875z"/>
<path d="m13.865234 3.5371094-.24414.9765625a7 7 0 0 1 4.378906 6.4863281 7 7 0 0 1 -4.380859 6.478516l.24414.974609a8 8 0 0 0 5.136719-7.453125 8 8 0 0 0 -5.134766-7.4628906z"/>
<path d="m3 8h2v6h-2z" fill-rule="evenodd"/>
<path d="m6 14 5 5h1v-16h-1l-5 5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 732 B

33
settingsdialog.py Normal file
View File

@ -0,0 +1,33 @@
from PyQt5 import QtWidgets
from ui_settingsdialog import UiSettingsDialog
class SettingsDialog(QtWidgets.QDialog, UiSettingsDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
@property
def server_url(self):
return self.server_edit.text()
@server_url.setter
def server_url(self, url):
self.server_edit.setText(url)
@property
def username(self):
return self.username_edit.text()
@username.setter
def username(self, username):
self.username_edit.setText(username)
@property
def password(self):
return self.password_edit.text()
@password.setter
def password(self, password):
self.password_edit.setText(password)

107
settingsdialog.ui Normal file
View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>391</width>
<height>202</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="server_group_box">
<property name="title">
<string>Server Details</string>
</property>
<layout class="QFormLayout" name="server_layout">
<item row="0" column="0">
<widget class="QLabel" name="server_label">
<property name="text">
<string>Server URL</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="server_edit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="username_label">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="username_edit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="password_edit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

6
threads.py Normal file
View File

@ -0,0 +1,6 @@
"""
The :mod:`threads` module contains functions to make working with QThreads easier
"""
from PyQt5 import QtCore

217
ui_mainwindow.py Normal file
View File

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
LIST_WIDGET_STYLES = """
QListWidget {
border: 0;
}
"""
class UiMainWindow(object):
def setup_ui(self):
self.setObjectName("MainWindow")
self.resize(719, 574)
self.setStyleSheet(LIST_WIDGET_STYLES)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.centralLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.centralLayout.setContentsMargins(0, 0, 0, 0)
self.centralLayout.setSpacing(0)
self.centralLayout.setObjectName("centralLayout")
self.playbackWidget = QtWidgets.QWidget(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.playbackWidget.sizePolicy().hasHeightForWidth())
self.playbackWidget.setSizePolicy(sizePolicy)
self.playbackWidget.setMinimumSize(QtCore.QSize(0, 36))
self.playbackWidget.setObjectName("playbackWidget")
self.playbackLayout = QtWidgets.QHBoxLayout(self.playbackWidget)
self.playbackLayout.setContentsMargins(0, 0, 0, 0)
self.playbackLayout.setSpacing(0)
self.playbackLayout.setObjectName("playbackLayout")
self.toggleButton = QtWidgets.QToolButton(self.playbackWidget)
self.toggleButton.setIcon(QtGui.QIcon.fromTheme("expand"))
self.toggleButton.setAutoRaise(True)
self.toggleButton.setObjectName("toggleButton")
self.playbackLayout.addWidget(self.toggleButton)
self.previousButton = QtWidgets.QToolButton(self.playbackWidget)
self.previousButton.setIcon(QtGui.QIcon.fromTheme("media-skip-backward"))
self.previousButton.setAutoRaise(True)
self.previousButton.setObjectName("previousButton")
self.playbackLayout.addWidget(self.previousButton)
self.playButton = QtWidgets.QToolButton(self.playbackWidget)
self.playButton.setIcon(QtGui.QIcon.fromTheme("media-playback-start"))
self.playButton.setAutoRaise(True)
self.playButton.setObjectName("playButton")
self.playbackLayout.addWidget(self.playButton)
self.nextButton = QtWidgets.QToolButton(self.playbackWidget)
self.nextButton.setIcon(QtGui.QIcon.fromTheme("media-skip-forward"))
self.nextButton.setAutoRaise(True)
self.nextButton.setObjectName("nextButton")
self.playbackLayout.addWidget(self.nextButton)
self.positionLabel = QtWidgets.QLabel(self.playbackWidget)
self.positionLabel.setAlignment(QtCore.Qt.AlignCenter)
self.positionLabel.setObjectName("positionLabel")
self.playbackLayout.addWidget(self.positionLabel)
self.positionSlider = QtWidgets.QSlider(self.playbackWidget)
self.positionSlider.setOrientation(QtCore.Qt.Horizontal)
self.positionSlider.setObjectName("positionSlider")
self.playbackLayout.addWidget(self.positionSlider)
self.shuffleButton = QtWidgets.QToolButton(self.playbackWidget)
self.shuffleButton.setIcon(QtGui.QIcon.fromTheme("media-playlist-normal"))
self.shuffleButton.setAutoRaise(True)
self.shuffleButton.setObjectName("shuffleButton")
self.playbackLayout.addWidget(self.shuffleButton)
self.repeatButton = QtWidgets.QToolButton(self.playbackWidget)
self.repeatButton.setIcon(QtGui.QIcon.fromTheme("media-repeat-none"))
self.repeatButton.setAutoRaise(True)
self.repeatButton.setObjectName("repeatButton")
self.playbackLayout.addWidget(self.repeatButton)
self.muteButton = QtWidgets.QToolButton(self.playbackWidget)
self.muteButton.setIcon(QtGui.QIcon.fromTheme("player-volume"))
self.muteButton.setCheckable(True)
self.muteButton.setAutoRaise(True)
self.muteButton.setObjectName("muteButton")
self.playbackLayout.addWidget(self.muteButton)
self.volumeSlider = QtWidgets.QSlider(self.playbackWidget)
self.volumeSlider.setOrientation(QtCore.Qt.Horizontal)
self.volumeSlider.setObjectName("volumeSlider")
self.playbackLayout.addWidget(self.volumeSlider)
self.menuButton = QtWidgets.QToolButton(self.playbackWidget)
self.menuButton.setIcon(QtGui.QIcon.fromTheme("application-menu"))
self.menuButton.setPopupMode(QtWidgets.QToolButton.DelayedPopup)
self.menuButton.setAutoRaise(True)
self.menuButton.setObjectName("menuButton")
self.playbackLayout.addWidget(self.menuButton)
self.centralLayout.addWidget(self.playbackWidget)
self.splitter = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setHandleWidth(1)
self.splitter.setObjectName("splitter")
self.viewListWidget = QtWidgets.QListWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.viewListWidget.sizePolicy().hasHeightForWidth())
self.viewListWidget.setSizePolicy(sizePolicy)
self.viewListWidget.setObjectName("viewListWidget")
self.stackedWidget = QtWidgets.QStackedWidget(self.splitter)
self.stackedWidget.setObjectName("stackedWidget")
self.albumPage = QtWidgets.QWidget()
self.albumPage.setObjectName("albumPage")
self.albumPageLayout = QtWidgets.QVBoxLayout(self.albumPage)
self.albumPageLayout.setContentsMargins(0, 0, 0, 0)
self.albumPageLayout.setSpacing(0)
self.albumPageLayout.setObjectName("albumPageLayout")
self.albumPageTitleLayout = QtWidgets.QHBoxLayout()
self.albumPageTitleLayout.setContentsMargins(0, 0, 0, 0)
self.albumPageTitleLayout.setSpacing(0)
self.albumPageTitleLayout.setObjectName("albumPageTitleLayout")
self.albumPageIconLabel = QtWidgets.QLabel(self.albumPage)
self.albumPageIconLabel.setPixmap(
QtGui.QIcon.fromTheme('view-media-album-cover').pixmap(self.albumPageIconLabel.geometry().height()))
self.albumPageTitleLayout.addWidget(self.albumPageIconLabel)
self.albumPageTitleLabel = QtWidgets.QLabel(self.albumPage)
self.albumPageTitleLayout.addWidget(self.albumPageTitleLabel)
self.albumPageLayout.addLayout(self.albumPageTitleLayout)
self.albumListWidget = QtWidgets.QListWidget(self.albumPage)
self.albumListWidget.setObjectName("albumListWidget")
self.albumListWidget.setSortingEnabled(True)
# self.albumListWidget.setViewMode(QtWidgets.QListView.IconMode)
# self.albumListWidget.setMovement(QtWidgets.QListView.Static)
# self.albumListWidget.setGridSize(QtCore.QSize(128, 128))
self.albumPageLayout.addWidget(self.albumListWidget)
self.stackedWidget.addWidget(self.albumPage)
self.artistPage = QtWidgets.QWidget()
self.artistPage.setObjectName("artistPage")
self.artistPageLayout = QtWidgets.QVBoxLayout(self.artistPage)
self.artistPageLayout.setContentsMargins(0, 0, 0, 0)
self.artistPageLayout.setSpacing(0)
self.artistPageLayout.setObjectName("artistPageLayout")
self.artistListWidget = QtWidgets.QListWidget(self.artistPage)
self.artistListWidget.setObjectName("artistListWidget")
self.artistListWidget.setSortingEnabled(True)
self.artistListWidget.setViewMode(QtWidgets.QListView.IconMode)
self.artistListWidget.setMovement(QtWidgets.QListView.Static)
self.artistPageLayout.addWidget(self.artistListWidget)
self.stackedWidget.addWidget(self.artistPage)
self.trackPage = QtWidgets.QWidget()
self.trackPage.setObjectName("trackPage")
self.trackPageLayout = QtWidgets.QVBoxLayout(self.trackPage)
self.trackPageLayout.setContentsMargins(0, 0, 0, 0)
self.trackPageLayout.setSpacing(0)
self.trackPageLayout.setObjectName("trackPageLayout")
self.trackListWidget = QtWidgets.QListWidget(self.trackPage)
self.trackListWidget.setObjectName("trackListWidget")
self.trackListWidget.setSortingEnabled(True)
self.trackListWidget.setViewMode(QtWidgets.QListView.IconMode)
self.trackListWidget.setMovement(QtWidgets.QListView.Static)
self.trackPageLayout.addWidget(self.trackListWidget)
self.stackedWidget.addWidget(self.trackPage)
self.playlistListWidget = QtWidgets.QListWidget(self.splitter)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.playlistListWidget.sizePolicy().hasHeightForWidth())
self.playlistListWidget.setSizePolicy(sizePolicy)
self.playlistListWidget.setObjectName("playlistListWidget")
self.centralLayout.addWidget(self.splitter)
self.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(self)
self.statusbar.setObjectName("statusbar")
self.setStatusBar(self.statusbar)
self.actionPlay = QtWidgets.QAction(self)
self.actionPlay.setObjectName("actionPlay")
self.mainMenu = QtWidgets.QMenu(self)
self.settingsAction = self.mainMenu.addAction(QtGui.QIcon.fromTheme('configure'), '')
self.albumsListItem = QtWidgets.QListWidgetItem(QtGui.QIcon.fromTheme('view-media-album-cover'), '')
self.artistsListItem = QtWidgets.QListWidgetItem(QtGui.QIcon.fromTheme('view-media-artist'), '')
self.tracksListItem = QtWidgets.QListWidgetItem(QtGui.QIcon.fromTheme('view-media-track'), '')
for item in [self.albumsListItem, self.artistsListItem, self.tracksListItem]:
self.viewListWidget.addItem(item)
self.splitter.setStretchFactor(1, 1)
self.retranslate_ui()
self.menuButton.clicked.connect(self.on_menu_button_clicked)
self.viewListWidget.currentRowChanged.connect(self.on_view_list_current_row_changed)
self.viewListWidget.setCurrentRow(0)
QtCore.QMetaObject.connectSlotsByName(self)
self.viewListWidget.setFocus()
def retranslate_ui(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("MainWindow", "PyFunkwhale"))
self.toggleButton.setText(_translate("MainWindow", "..."))
self.previousButton.setText(_translate("MainWindow", "..."))
self.playButton.setText(_translate("MainWindow", "..."))
self.nextButton.setText(_translate("MainWindow", "..."))
self.positionLabel.setText(_translate("MainWindow", "01:43"))
self.shuffleButton.setText(_translate("MainWindow", "..."))
self.repeatButton.setText(_translate("MainWindow", "..."))
self.muteButton.setText(_translate("MainWindow", "..."))
self.menuButton.setText(_translate("MainWindow", "..."))
self.actionPlay.setText(_translate("MainWindow", "&Play"))
self.actionPlay.setToolTip(_translate("MainWindow", "Play"))
self.settingsAction.setText(_translate("MainWindow", "&Configure"))
self.albumsListItem.setText(_translate("MainWindow", "Albums"))
self.albumPageTitleLabel.setText(_translate("MainWindow", "Albums"))
self.artistsListItem.setText(_translate("MainWindow", "Artists"))
self.tracksListItem.setText(_translate("MainWindow", "Tracks"))
def on_menu_button_clicked(self):
pos = self.mapToGlobal(self.menuButton.geometry().bottomRight())
pos.setX(pos.x() - self.mainMenu.geometry().width())
self.mainMenu.exec(pos)
def on_view_list_current_row_changed(self, current_row):
self.stackedWidget.setCurrentIndex(current_row)

52
ui_settingsdialog.py Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtWidgets
class UiSettingsDialog(object):
def setup_ui(self):
self.setObjectName("SettingsDialog")
self.resize(391, 202)
self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.server_group_box = QtWidgets.QGroupBox(self)
self.server_group_box.setObjectName("server_group_box")
self.server_layout = QtWidgets.QFormLayout(self.server_group_box)
self.server_layout.setObjectName("server_layout")
self.server_label = QtWidgets.QLabel(self.server_group_box)
self.server_label.setObjectName("server_label")
self.server_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.server_label)
self.server_edit = QtWidgets.QLineEdit(self.server_group_box)
self.server_edit.setObjectName("server_edit")
self.server_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.server_edit)
self.username_label = QtWidgets.QLabel(self.server_group_box)
self.username_label.setObjectName("username_label")
self.server_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.username_label)
self.username_edit = QtWidgets.QLineEdit(self.server_group_box)
self.username_edit.setObjectName("username_edit")
self.server_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.username_edit)
self.label_3 = QtWidgets.QLabel(self.server_group_box)
self.label_3.setObjectName("label_3")
self.server_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
self.password_edit = QtWidgets.QLineEdit(self.server_group_box)
self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password)
self.password_edit.setObjectName("password_edit")
self.server_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.password_edit)
self.verticalLayout.addWidget(self.server_group_box)
self.buttonBox = QtWidgets.QDialogButtonBox(self)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslate_ui()
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
QtCore.QMetaObject.connectSlotsByName(self)
def retranslate_ui(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("SettingsDialog", "Settings"))
self.server_group_box.setTitle(_translate("SettingsDialog", "Server Details"))
self.server_label.setText(_translate("SettingsDialog", "Server URL"))
self.username_label.setText(_translate("SettingsDialog", "Username"))
self.label_3.setText(_translate("SettingsDialog", "Password"))