1
0
Fork 0
mirror of synced 2024-05-19 12:02:54 +12:00
Rare/rare/components/main_window.py
loathingKernel 8ebcc3a700 LoadingWidget: Start playing movie once the widget is visible if autostart is enabled
Fixes a subtle bug that would cause increased CPU usage due to spawning
multiple singleshot times with very short timeouts (15ms) until the Store
tab was loaded.
2024-05-15 22:48:52 +03:00

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
from PyQt5.QtWidgets import (
QMainWindow,
QApplication,
QStatusBar,
QScrollArea,
QScroller,
QComboBox,
QMessageBox,
QLabel,
QWidget,
QHBoxLayout,
)
from rare.models.options import options
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.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(*options.save_size):
width, height = self.settings.value(*options.window_size)
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.singleton_timer = QTimer(self)
self.singleton_timer.setInterval(1000)
self.singleton_timer.timeout.connect(self.timer_finished)
self.singleton_timer.start()
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(*options.save_size):
size = self.size().width(), self.size().height()
self.settings.setValue(options.window_size.key, 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()
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.setProperty("workerType", info.worker_type)
label.setFixedWidth(150)
label.setContentsMargins(3, 0, 3, 0)
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):
with open(file_path, "r") as file:
action = file.read()
if action.startswith("show"):
self.show()
os.remove(file_path)
self.singleton_timer.start()
@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(*options.sys_tray):
self.hide()
e.ignore()
return
# FIXME: Move this to RareCore once the download manager is implemented
if self.rcore.queue_threadpool.activeThreadCount():
reply = QMessageBox.question(
self,
self.tr("Quit {}?").format(QApplication.applicationName()),
self.tr(
"There are currently running operations. "
"Rare cannot exit until they are completed.\n\n"
"Do you want to clear the queue?"
),
buttons=(QMessageBox.Yes | QMessageBox.No),
defaultButton=QMessageBox.No,
)
if reply == QMessageBox.Yes:
self.rcore.queue_threadpool.clear()
for qw in self.rcore.queued_workers():
self.rcore.dequeue_worker(qw)
self.update_statusbar()
e.ignore()
return
elif self.tab_widget.downloads_tab.is_download_active:
reply = QMessageBox.question(
self,
self.tr("Quit {}?").format(QApplication.applicationName()),
self.tr(
"There is an active download. "
"Quitting Rare now will stop the download.\n\n"
"Are you sure you want to quit?"
),
buttons=(QMessageBox.Yes | QMessageBox.No),
defaultButton=QMessageBox.No,
)
if reply == QMessageBox.Yes:
self.tab_widget.downloads_tab.stop_download(omit_queue=True)
else:
e.ignore()
return
# FIXME: End of FIXME
self.singleton_timer.stop()
self.tray_icon.deleteLater()
self.hide()
self.exit_app.emit(self.__exit_code)
super(MainWindow, self).closeEvent(e)