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 + + + + + + + + + + + 0 + 0 + 719 + 30 + + + + + + + &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"))