diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ba1ec25
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+*.py[cd]
diff --git a/mainwindow.py b/mainwindow.py
new file mode 100644
index 0000000..dd51c48
--- /dev/null
+++ b/mainwindow.py
@@ -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))
diff --git a/mainwindow.ui b/mainwindow.ui
new file mode 100644
index 0000000..04ecd5d
--- /dev/null
+++ b/mainwindow.ui
@@ -0,0 +1,329 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 719
+ 574
+
+
+
+ MainWindow
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 36
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ 01:43
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ QToolButton::DelayedPopup
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::Horizontal
+
+
+ 1
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+ &Play
+
+
+ Play
+
+
+
+
+
+
+
+
diff --git a/pyfunkwhale.py b/pyfunkwhale.py
new file mode 100644
index 0000000..c290c55
--- /dev/null
+++ b/pyfunkwhale.py
@@ -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()
diff --git a/resources/media-album-cover-manager-amarok.svg b/resources/media-album-cover-manager-amarok.svg
new file mode 100644
index 0000000..932cf18
--- /dev/null
+++ b/resources/media-album-cover-manager-amarok.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/media-album-track.svg b/resources/media-album-track.svg
new file mode 100644
index 0000000..403d9af
--- /dev/null
+++ b/resources/media-album-track.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/media-playback-pause.svg b/resources/media-playback-pause.svg
new file mode 100644
index 0000000..49d9941
--- /dev/null
+++ b/resources/media-playback-pause.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-playback-start.svg b/resources/media-playback-start.svg
new file mode 100644
index 0000000..bbe84eb
--- /dev/null
+++ b/resources/media-playback-start.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-playback-stop.svg b/resources/media-playback-stop.svg
new file mode 100644
index 0000000..67d10e8
--- /dev/null
+++ b/resources/media-playback-stop.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-playlist-append.svg b/resources/media-playlist-append.svg
new file mode 100644
index 0000000..1ff818c
--- /dev/null
+++ b/resources/media-playlist-append.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/media-playlist-normal.svg b/resources/media-playlist-normal.svg
new file mode 100644
index 0000000..4aa4dbd
--- /dev/null
+++ b/resources/media-playlist-normal.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/media-playlist-repeat.svg b/resources/media-playlist-repeat.svg
new file mode 100644
index 0000000..e2d5091
--- /dev/null
+++ b/resources/media-playlist-repeat.svg
@@ -0,0 +1,10 @@
+
diff --git a/resources/media-playlist-shuffle.svg b/resources/media-playlist-shuffle.svg
new file mode 100644
index 0000000..bb0fcf1
--- /dev/null
+++ b/resources/media-playlist-shuffle.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/media-repeat-none.svg b/resources/media-repeat-none.svg
new file mode 100644
index 0000000..b11d461
--- /dev/null
+++ b/resources/media-repeat-none.svg
@@ -0,0 +1,12 @@
+
diff --git a/resources/media-repeat-single.svg b/resources/media-repeat-single.svg
new file mode 100644
index 0000000..67318e7
--- /dev/null
+++ b/resources/media-repeat-single.svg
@@ -0,0 +1,12 @@
+
diff --git a/resources/media-seek-backward.svg b/resources/media-seek-backward.svg
new file mode 100644
index 0000000..092c51a
--- /dev/null
+++ b/resources/media-seek-backward.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-seek-forward.svg b/resources/media-seek-forward.svg
new file mode 100644
index 0000000..c1903b2
--- /dev/null
+++ b/resources/media-seek-forward.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-skip-backward.svg b/resources/media-skip-backward.svg
new file mode 100644
index 0000000..e4f1c48
--- /dev/null
+++ b/resources/media-skip-backward.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/media-skip-forward.svg b/resources/media-skip-forward.svg
new file mode 100644
index 0000000..97c4e72
--- /dev/null
+++ b/resources/media-skip-forward.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/menu_new.svg b/resources/menu_new.svg
new file mode 100644
index 0000000..f685e76
--- /dev/null
+++ b/resources/menu_new.svg
@@ -0,0 +1,14 @@
+
diff --git a/resources/pyfunkwhale.qrc b/resources/pyfunkwhale.qrc
new file mode 100644
index 0000000..6622da5
--- /dev/null
+++ b/resources/pyfunkwhale.qrc
@@ -0,0 +1,23 @@
+
+
+ media-playback-start.svg
+ media-playback-pause.svg
+ media-playback-stop.svg
+ media-playlist-normal.svg
+ media-playlist-repeat.svg
+ media-playlist-shuffle.svg
+ media-repeat-none.svg
+ media-repeat-single.svg
+ media-seek-backward.svg
+ media-seek-forward.svg
+ media-skip-backward.svg
+ media-skip-forward.svg
+ media-album-track.svg
+ media-album-cover-manager-amarok.svg
+ media-playlist-append.svg
+
+
+ menu_new.svg
+ speaker.svg
+
+
diff --git a/resources/speaker.svg b/resources/speaker.svg
new file mode 100644
index 0000000..e964066
--- /dev/null
+++ b/resources/speaker.svg
@@ -0,0 +1,13 @@
+
diff --git a/settingsdialog.py b/settingsdialog.py
new file mode 100644
index 0000000..88f6323
--- /dev/null
+++ b/settingsdialog.py
@@ -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)
diff --git a/settingsdialog.ui b/settingsdialog.ui
new file mode 100644
index 0000000..17b3e5b
--- /dev/null
+++ b/settingsdialog.ui
@@ -0,0 +1,107 @@
+
+
+ SettingsDialog
+
+
+
+ 0
+ 0
+ 391
+ 202
+
+
+
+ Dialog
+
+
+ -
+
+
+ Server Details
+
+
+
-
+
+
+ Server URL
+
+
+
+ -
+
+
+ -
+
+
+ Username
+
+
+
+ -
+
+
+ -
+
+
+ Password
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ SettingsDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ SettingsDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/threads.py b/threads.py
new file mode 100644
index 0000000..c62f0ea
--- /dev/null
+++ b/threads.py
@@ -0,0 +1,6 @@
+"""
+The :mod:`threads` module contains functions to make working with QThreads easier
+"""
+from PyQt5 import QtCore
+
+
diff --git a/ui_mainwindow.py b/ui_mainwindow.py
new file mode 100644
index 0000000..cb7f580
--- /dev/null
+++ b/ui_mainwindow.py
@@ -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)
diff --git a/ui_settingsdialog.py b/ui_settingsdialog.py
new file mode 100644
index 0000000..cfee868
--- /dev/null
+++ b/ui_settingsdialog.py
@@ -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"))