e40cfaafac
The active workers are placed in a static container on the left side. The queued workers are placed in a scrollarea on the right side of the status bar. The scrollbars are disabled but dragging works. Exiting with active workers will pop up a message for confirmation. Rare will clean the queued workers but the active ones will be waited on until they finish.
267 lines
10 KiB
Python
267 lines
10 KiB
Python
import os
|
|
from logging import getLogger
|
|
|
|
from PyQt5.QtCore import Qt, QSettings, QTimer, QSize, pyqtSignal, pyqtSlot
|
|
from PyQt5.QtGui import QCloseEvent, QCursor, QColor
|
|
from PyQt5.QtWidgets import (
|
|
QMainWindow,
|
|
QApplication,
|
|
QStatusBar,
|
|
QScrollArea,
|
|
QScroller,
|
|
QComboBox,
|
|
QMessageBox,
|
|
QLabel,
|
|
QWidget,
|
|
QHBoxLayout,
|
|
)
|
|
|
|
from rare.components.tabs import MainTabWidget
|
|
from rare.components.tray_icon import TrayIcon
|
|
from rare.shared import RareCore
|
|
from rare.shared.workers.worker import QueueWorkerState
|
|
from rare.utils.paths import lock_file
|
|
from rare.widgets.elide_label import ElideLabel
|
|
|
|
logger = getLogger("MainWindow")
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
# int: exit code
|
|
exit_app: pyqtSignal = pyqtSignal(int)
|
|
|
|
def __init__(self, parent=None):
|
|
self.__exit_code = 0
|
|
self.__accept_close = False
|
|
self._window_launched = False
|
|
super(MainWindow, self).__init__(parent=parent)
|
|
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
|
self.rcore = RareCore.instance()
|
|
self.core = RareCore.instance().core()
|
|
self.signals = RareCore.instance().signals()
|
|
self.args = RareCore.instance().args()
|
|
self.settings = QSettings()
|
|
|
|
self.setWindowTitle("Rare - GUI for legendary")
|
|
self.tab_widget = MainTabWidget(self)
|
|
self.tab_widget.exit_app.connect(self.on_exit_app)
|
|
self.setCentralWidget(self.tab_widget)
|
|
|
|
# Set up status bar stuff (jumping through a lot of hoops)
|
|
self.status_bar = QStatusBar(self)
|
|
self.setStatusBar(self.status_bar)
|
|
|
|
self.active_label = QLabel(self.tr("Active:"), self.status_bar)
|
|
# lk: set top and botton margins to accommodate border for scroll area labels
|
|
self.active_label.setContentsMargins(5, 1, 0, 1)
|
|
self.status_bar.addWidget(self.active_label)
|
|
|
|
self.active_container = QWidget(self.status_bar)
|
|
active_layout = QHBoxLayout(self.active_container)
|
|
active_layout.setContentsMargins(0, 0, 0, 0)
|
|
active_layout.setSizeConstraint(QHBoxLayout.SetFixedSize)
|
|
self.status_bar.addWidget(self.active_container, stretch=0)
|
|
|
|
self.queued_label = QLabel(self.tr("Queued:"), self.status_bar)
|
|
# lk: set top and botton margins to accommodate border for scroll area labels
|
|
self.queued_label.setContentsMargins(5, 1, 0, 1)
|
|
self.status_bar.addPermanentWidget(self.queued_label)
|
|
|
|
self.queued_scroll = QScrollArea(self.status_bar)
|
|
self.queued_scroll.setFrameStyle(QScrollArea.NoFrame)
|
|
self.queued_scroll.setWidgetResizable(True)
|
|
self.queued_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
self.queued_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
self.queued_scroll.setFixedHeight(self.queued_label.sizeHint().height())
|
|
self.status_bar.addPermanentWidget(self.queued_scroll, stretch=1)
|
|
|
|
self.queued_container = QWidget(self.queued_scroll)
|
|
queued_layout = QHBoxLayout(self.queued_container)
|
|
queued_layout.setContentsMargins(0, 0, 0, 0)
|
|
queued_layout.setSizeConstraint(QHBoxLayout.SetFixedSize)
|
|
|
|
self.active_label.setVisible(False)
|
|
self.active_container.setVisible(False)
|
|
self.queued_label.setVisible(False)
|
|
self.queued_scroll.setVisible(False)
|
|
|
|
self.signals.application.update_statusbar.connect(self.update_statusbar)
|
|
self.signals.application.update_statusbar.connect(self.update_statusbar)
|
|
|
|
# self.status_timer = QTimer(self)
|
|
# self.status_timer.timeout.connect(self.update_statusbar)
|
|
# self.status_timer.setInterval(5000)
|
|
# self.status_timer.start()
|
|
|
|
width, height = 1280, 720
|
|
if self.settings.value("save_size", False, bool):
|
|
width, height = self.settings.value("window_size", (width, height), tuple)
|
|
|
|
self.resize(width, height)
|
|
|
|
if not self.args.offline:
|
|
try:
|
|
from rare.utils.rpc import DiscordRPC
|
|
|
|
self.rpc = DiscordRPC()
|
|
except ModuleNotFoundError:
|
|
logger.warning("Discord RPC module not found")
|
|
|
|
self.timer = QTimer()
|
|
self.timer.timeout.connect(self.timer_finished)
|
|
self.timer.start(1000)
|
|
|
|
self.tray_icon: TrayIcon = TrayIcon(self)
|
|
self.tray_icon.exit_app.connect(self.on_exit_app)
|
|
self.tray_icon.show_app.connect(self.show)
|
|
self.tray_icon.activated.connect(lambda r: self.toggle() if r == self.tray_icon.DoubleClick else None)
|
|
|
|
# enable kinetic scrolling
|
|
for scroll_area in self.findChildren(QScrollArea):
|
|
if not scroll_area.property("no_kinetic_scroll"):
|
|
QScroller.grabGesture(scroll_area.viewport(), QScroller.LeftMouseButtonGesture)
|
|
|
|
# fix scrolling
|
|
for combo_box in scroll_area.findChildren(QComboBox):
|
|
combo_box.wheelEvent = lambda e: e.ignore()
|
|
|
|
def center_window(self):
|
|
# get the margins of the decorated window
|
|
margins = self.windowHandle().frameMargins()
|
|
# get the screen the cursor is on
|
|
current_screen = QApplication.screenAt(QCursor.pos())
|
|
if not current_screen:
|
|
current_screen = QApplication.primaryScreen()
|
|
# get the available screen geometry (excludes panels/docks)
|
|
screen_rect = current_screen.availableGeometry()
|
|
decor_width = margins.left() + margins.right()
|
|
decor_height = margins.top() + margins.bottom()
|
|
window_size = QSize(self.width(), self.height()).boundedTo(
|
|
screen_rect.size() - QSize(decor_width, decor_height)
|
|
)
|
|
|
|
self.resize(window_size)
|
|
self.move(screen_rect.center() - self.rect().adjusted(0, 0, decor_width, decor_height).center())
|
|
|
|
@pyqtSlot()
|
|
def show(self) -> None:
|
|
super(MainWindow, self).show()
|
|
if not self._window_launched:
|
|
self.center_window()
|
|
self._window_launched = True
|
|
|
|
def hide(self) -> None:
|
|
if self.settings.value("save_size", False, bool):
|
|
size = self.size().width(), self.size().height()
|
|
self.settings.setValue("window_size", size)
|
|
super(MainWindow, self).hide()
|
|
|
|
def toggle(self):
|
|
if self.isHidden():
|
|
self.show()
|
|
else:
|
|
self.hide()
|
|
|
|
@pyqtSlot()
|
|
def update_statusbar(self):
|
|
self.active_label.setVisible(False)
|
|
self.active_container.setVisible(False)
|
|
self.queued_label.setVisible(False)
|
|
self.queued_scroll.setVisible(False)
|
|
for label in self.active_container.findChildren(QLabel, options=Qt.FindDirectChildrenOnly):
|
|
self.active_container.layout().removeWidget(label)
|
|
label.deleteLater()
|
|
for label in self.queued_container.findChildren(QLabel, options=Qt.FindDirectChildrenOnly):
|
|
self.queued_container.layout().removeWidget(label)
|
|
label.deleteLater()
|
|
stylesheet = """
|
|
QLabel#QueueWorkerLabel {{
|
|
border-radius: 3px;
|
|
border: 1px solid {br_color};
|
|
background-color: {bg_color};
|
|
}}
|
|
"""
|
|
color = {
|
|
"Verify": QColor("#d6af57"),
|
|
"Move": QColor("#41cad9"),
|
|
}
|
|
for info in self.rcore.queue_info():
|
|
label = ElideLabel(info.app_title)
|
|
label.setObjectName("QueueWorkerLabel")
|
|
label.setToolTip(f"<b>{info.worker_type}</b>: {info.app_title}")
|
|
label.setFixedWidth(150)
|
|
label.setContentsMargins(3, 0, 3, 0)
|
|
label.setStyleSheet(stylesheet.format(
|
|
br_color=color[info.worker_type].darker(200).name(),
|
|
bg_color=color[info.worker_type].darker(400).name(),
|
|
))
|
|
if info.state == QueueWorkerState.ACTIVE:
|
|
self.active_container.layout().addWidget(label)
|
|
self.active_label.setVisible(True)
|
|
self.active_container.setVisible(True)
|
|
elif info.state == QueueWorkerState.QUEUED:
|
|
self.queued_container.layout().addWidget(label)
|
|
self.queued_label.setVisible(True)
|
|
self.queued_scroll.setVisible(True)
|
|
|
|
def timer_finished(self):
|
|
file_path = lock_file()
|
|
if os.path.exists(file_path):
|
|
file = open(file_path, "r")
|
|
action = file.read()
|
|
file.close()
|
|
if action.startswith("show"):
|
|
self.show()
|
|
os.remove(file_path)
|
|
self.timer.start(1000)
|
|
|
|
@pyqtSlot()
|
|
@pyqtSlot(int)
|
|
def on_exit_app(self, exit_code=0) -> None:
|
|
self.__exit_code = exit_code
|
|
self.close()
|
|
|
|
def close(self) -> bool:
|
|
self.__accept_close = True
|
|
return super(MainWindow, self).close()
|
|
|
|
def closeEvent(self, e: QCloseEvent) -> None:
|
|
# lk: `accept_close` is set to `True` by the `close()` method, overrides exiting to tray in `closeEvent()`
|
|
# lk: ensures exiting instead of hiding when `close()` is called programmatically
|
|
if not self.__accept_close:
|
|
if self.settings.value("sys_tray", False, bool):
|
|
self.hide()
|
|
e.ignore()
|
|
return
|
|
|
|
print(self.rcore.queue_threadpool.activeThreadCount())
|
|
# FIXME: Move this to RareCore once the download manager is implemented
|
|
if not self.args.offline and (
|
|
self.tab_widget.downloads_tab.is_download_active or self.rcore.queue_threadpool.activeThreadCount()
|
|
):
|
|
reply = QMessageBox.question(
|
|
self,
|
|
self.tr("Quit {}?").format(QApplication.applicationName()),
|
|
self.tr("There are active operations. Are you sure you want to quit?"),
|
|
buttons=(QMessageBox.Yes | QMessageBox.No),
|
|
defaultButton=QMessageBox.No,
|
|
)
|
|
if reply == QMessageBox.Yes:
|
|
if self.tab_widget.downloads_tab.is_download_active:
|
|
self.tab_widget.downloads_tab.stop_download(omit_queue=True)
|
|
if self.rcore.queue_threadpool.activeThreadCount():
|
|
self.rcore.queue_threadpool.clear()
|
|
for qw in self.rcore.queued_workers():
|
|
self.rcore.queue_workers.remove(qw)
|
|
self.update_statusbar()
|
|
self.rcore.queue_threadpool.waitForDone()
|
|
else:
|
|
e.ignore()
|
|
return
|
|
# FIXME: End of FIXME
|
|
self.timer.stop()
|
|
self.tray_icon.deleteLater()
|
|
self.hide()
|
|
self.exit_app.emit(self.__exit_code)
|
|
super(MainWindow, self).closeEvent(e)
|
|
|