1
0
Fork 0
mirror of synced 2024-06-28 11:11:15 +12:00

App: Move legendary initialization to the singleton

App: Move tray to MainWindow
Shared: Add destructor for singleton instances
This commit is contained in:
loathingKernel 2022-09-04 01:14:43 +03:00
parent 4951743bbf
commit afcdc1dea1
5 changed files with 194 additions and 162 deletions

View file

@ -13,14 +13,19 @@ from typing import Optional
import legendary import legendary
import requests.exceptions import requests.exceptions
from PyQt5.QtCore import QThreadPool, QTimer, QT_VERSION_STR, PYQT_VERSION_STR from PyQt5.QtCore import QThreadPool, QTimer, QT_VERSION_STR, PYQT_VERSION_STR
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox from PyQt5.QtWidgets import QApplication, QMessageBox
from requests import HTTPError from requests import HTTPError
import rare import rare
from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow from rare.components.main_window import MainWindow
from rare.components.tray_icon import TrayIcon from rare.shared import (
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton LegendaryCoreSingleton,
GlobalSignalsSingleton,
ArgumentsSingleton,
ApiResultsSingleton,
clear_singleton_instance
)
from rare.shared.image_manager import ImageManagerSingleton from rare.shared.image_manager import ImageManagerSingleton
from rare.utils import legendary_utils, config_helper from rare.utils import legendary_utils, config_helper
from rare.utils.paths import cache_dir, tmp_dir from rare.utils.paths import cache_dir, tmp_dir
@ -54,64 +59,21 @@ def excepthook(exc_type, exc_value, exc_tb):
class App(RareApp): class App(RareApp):
mainwindow: Optional[MainWindow] = None
tray_icon: Optional[QSystemTrayIcon] = None
def __init__(self, args: Namespace): def __init__(self, args: Namespace):
super(App, self).__init__() super(App, self).__init__()
self.core = LegendaryCoreSingleton()
self.args = ArgumentsSingleton(args) # add some options self.args = ArgumentsSingleton(args) # add some options
self.window_launched = False
# init Legendary
try:
self.core = LegendaryCoreSingleton(init=True)
except configparser.MissingSectionHeaderError as e:
logger.warning(f"Config is corrupt: {e}")
if config_path := os.environ.get("XDG_CONFIG_HOME"):
path = os.path.join(config_path, "legendary")
else:
path = os.path.expanduser("~/.config/legendary")
with open(os.path.join(path, "config.ini"), "w") as config_file:
config_file.write("[Legendary]")
self.core = LegendaryCoreSingleton(init=True)
if "Legendary" not in self.core.lgd.config.sections():
self.core.lgd.config.add_section("Legendary")
self.core.lgd.save_config()
lang = self.settings.value("language", self.core.language_code, type=str) lang = self.settings.value("language", self.core.language_code, type=str)
self.load_translator(lang) self.load_translator(lang)
config_helper.init_config_handler(self.core)
# workaround if egl sync enabled, but no programdata_path
# programdata_path might be unset if logging in through the browser
if self.core.egl_sync_enabled:
if self.core.egl.programdata_path is None:
self.core.lgd.config.remove_option("Legendary", "egl_sync")
self.core.lgd.save_config()
else:
if not os.path.exists(self.core.egl.programdata_path):
self.core.lgd.config.remove_option("Legendary", "egl_sync")
self.core.lgd.save_config()
# set Application name for settings # set Application name for settings
self.launch_dialog = None self.mainwindow: Optional[MainWindow] = None
self.launch_dialog: Optional[LaunchDialog] = None
self.signals = GlobalSignalsSingleton(init=True) self.signals = GlobalSignalsSingleton(init=True)
self.image_manager = ImageManagerSingleton(init=True) self.image_manager = ImageManagerSingleton(init=True)
self.signals.exit_app.connect(self.exit_app)
self.signals.send_notification.connect(
lambda title: self.tray_icon.showMessage(
self.tr("Download finished"),
self.tr("Download finished. {} is playable now").format(title),
QSystemTrayIcon.Information,
4000,
)
if self.settings.value("notification", True, bool)
else None
)
# launch app # launch app
self.launch_dialog = LaunchDialog(parent=None) self.launch_dialog = LaunchDialog(parent=None)
self.launch_dialog.quit_app.connect(self.launch_dialog.close) self.launch_dialog.quit_app.connect(self.launch_dialog.close)
@ -140,12 +102,6 @@ class App(RareApp):
td = abs(dt_exp - dt_now) td = abs(dt_exp - dt_now)
self.timer.start(int(td.total_seconds() - 60) * 1000) self.timer.start(int(td.total_seconds() - 60) * 1000)
def show_mainwindow(self):
if self.window_launched:
self.mainwindow.show()
else:
self.mainwindow.show_window_centered()
def start_app(self): def start_app(self):
for igame in self.core.get_installed_list(): for igame in self.core.get_installed_list():
if not os.path.exists(igame.install_path): if not os.path.exists(igame.install_path):
@ -167,85 +123,27 @@ class App(RareApp):
logger.info(f"{igame.title} needs verification") logger.info(f"{igame.title} needs verification")
self.mainwindow = MainWindow() self.mainwindow = MainWindow()
self.tray_icon: TrayIcon = TrayIcon(self) self.mainwindow.exit_app.connect(self.exit_app)
self.tray_icon.exit_action.triggered.connect(self.exit_app)
self.tray_icon.start_rare.triggered.connect(self.show_mainwindow)
self.tray_icon.activated.connect(
lambda r: self.show_mainwindow()
if r == QSystemTrayIcon.DoubleClick
else None
)
if not self.args.silent: if not self.args.silent:
self.mainwindow.show_window_centered() self.mainwindow.show()
self.window_launched = True
if self.args.subparser == "launch":
if self.args.app_name in [
i.app_name for i in self.core.get_installed_list()
]:
logger.info(
f"Launching {self.core.get_installed_game(self.args.app_name).title}"
)
self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(
self.args.app_name
)
else:
logger.error(
f"Could not find {self.args.app_name} in Games or it is not installed"
)
QMessageBox.warning(
self.mainwindow,
"Warning",
self.tr(
"Could not find {} in installed games. Did you modify the shortcut? "
).format(self.args.app_name),
)
if self.args.test_start: if self.args.test_start:
self.exit_app(0) self.exit_app(0)
def tray(self, reason):
if reason == QSystemTrayIcon.DoubleClick:
self.mainwindow.show()
logger.info("Show App")
def exit_app(self, exit_code=0): def exit_app(self, exit_code=0):
# FIXME: Fix this with the download tab redesign
if self.mainwindow is not None:
if not self.args.offline and self.mainwindow.tab_widget.downloadTab.is_download_active:
question = QMessageBox.question(
self.mainwindow,
self.tr("Close"),
self.tr(
"There is a download active. Do you really want to exit app?"
),
QMessageBox.Yes,
QMessageBox.No,
)
if question == QMessageBox.No:
return
else:
# clear queue
self.mainwindow.tab_widget.downloadTab.queue_widget.update_queue([])
self.mainwindow.tab_widget.downloadTab.stop_download()
# FIXME: End of FIXME
self.mainwindow.timer.stop()
self.mainwindow.hide()
threadpool = QThreadPool.globalInstance() threadpool = QThreadPool.globalInstance()
threadpool.waitForDone() threadpool.waitForDone()
self.core.exit()
if self.mainwindow is not None:
self.mainwindow.close()
self.mainwindow.deleteLater()
self.mainwindow = None
if self.tray_icon is not None:
self.tray_icon.deleteLater()
self.tray_icon = None
if self.timer is not None: if self.timer is not None:
self.timer.stop() self.timer.stop()
self.timer.deleteLater() self.timer.deleteLater()
self.timer = None self.timer = None
if self.mainwindow is not None:
self.mainwindow.close()
self.mainwindow = None
clear_singleton_instance(self.signals)
clear_singleton_instance(self.args)
clear_singleton_instance(ApiResultsSingleton())
self.processEvents() self.processEvents()
shutil.rmtree(tmp_dir) shutil.rmtree(tmp_dir)
os.makedirs(tmp_dir) os.makedirs(tmp_dir)
@ -284,10 +182,16 @@ def start(args):
logger.info(f"Operating System: {platform.system()}") logger.info(f"Operating System: {platform.system()}")
while True: while True:
core = LegendaryCoreSingleton(init=True)
config_helper.init_config_handler(core)
app = App(args) app = App(args)
exit_code = app.exec_() exit_code = app.exec_()
# if not restart # if not restart
# restart app # restart app
del app del app
core.exit()
clear_singleton_instance(core)
if exit_code != -133742: if exit_code != -133742:
break break

View file

@ -1,18 +1,22 @@
import os import os
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import Qt, QSettings, QTimer, QSize from PyQt5.QtCore import Qt, QSettings, QTimer, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor from PyQt5.QtGui import QCloseEvent, QCursor
from PyQt5.QtWidgets import QMainWindow, QApplication, QStatusBar, QScrollArea, QScroller, QComboBox from PyQt5.QtWidgets import QMainWindow, QApplication, QStatusBar, QScrollArea, QScroller, QComboBox, QMessageBox
from rare.components.tabs import TabWidget from rare.components.tabs import TabWidget
from rare.components.tray_icon import TrayIcon
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton
from rare.utils.paths import data_dir from rare.utils.paths import data_dir
logger = getLogger("Window") logger = getLogger("MainWindow")
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
# int: exit code
exit_app: pyqtSignal = pyqtSignal(int)
def __init__(self, parent=None): def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent) super(MainWindow, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose)
@ -46,8 +50,42 @@ class MainWindow(QMainWindow):
self.timer.timeout.connect(self.timer_finished) self.timer.timeout.connect(self.timer_finished)
self.timer.start(1000) self.timer.start(1000)
def show_window_centered(self): self.signals.exit_app.connect(self.on_exit_app)
self.show() self.exit_code = 0
self.accept_close = False
self.tray_icon: TrayIcon = TrayIcon(self)
self.tray_icon.exit_action.triggered.connect(self.on_exit_app)
self.tray_icon.start_rare.triggered.connect(self.show)
self.tray_icon.activated.connect(
lambda r: self.toggle()
if r == self.tray_icon.DoubleClick
else None
)
self.signals.send_notification.connect(
lambda title: self.tray_icon.showMessage(
self.tr("Download finished"),
self.tr("Download finished. {} is playable now").format(title),
self.tray_icon.Information,
4000,
)
if self.settings.value("notification", True, bool)
else None
)
self.window_launched = False
# 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 # get the margins of the decorated window
margins = self.windowHandle().frameMargins() margins = self.windowHandle().frameMargins()
# get the screen the cursor is on # get the screen the cursor is on
@ -68,14 +106,23 @@ class MainWindow(QMainWindow):
- self.rect().adjusted(0, 0, decor_width, decor_height).center() - self.rect().adjusted(0, 0, decor_width, decor_height).center()
) )
# enable kinetic scrolling def show(self) -> None:
for scroll_area in self.findChildren(QScrollArea): super(MainWindow, self).show()
if not scroll_area.property("no_kinetic_scroll"): if not self.window_launched:
QScroller.grabGesture(scroll_area.viewport(), QScroller.LeftMouseButtonGesture) self.center_window()
self.window_launched = True
# fix scrolling def hide(self) -> None:
for combo_box in scroll_area.findChildren(QComboBox): if self.settings.value("save_size", False, bool):
combo_box.wheelEvent = lambda e: e.ignore() 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()
def timer_finished(self): def timer_finished(self):
file_path = os.path.join(data_dir, "lockfile") file_path = os.path.join(data_dir, "lockfile")
@ -88,15 +135,43 @@ class MainWindow(QMainWindow):
os.remove(file_path) os.remove(file_path)
self.timer.start(1000) self.timer.start(1000)
@pyqtSlot()
@pyqtSlot(int)
def on_exit_app(self, exit_code=0) -> None:
# FIXME: Fix this with the download tab redesign
if not self.args.offline and self.tab_widget.downloadTab.is_download_active:
question = QMessageBox.question(
self,
self.tr("Close"),
self.tr(
"There is a download active. Do you really want to exit app?"
),
QMessageBox.Yes,
QMessageBox.No,
)
if question == QMessageBox.No:
return
else:
# clear queue
self.tab_widget.downloadTab.queue_widget.update_queue([])
self.tab_widget.downloadTab.stop_download()
# FIXME: End of FIXME
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): def closeEvent(self, e: QCloseEvent):
if self.settings.value("save_size", False, bool): if not self.accept_close:
size = self.size().width(), self.size().height() if self.settings.value("sys_tray", True, bool):
self.settings.setValue("window_size", size) self.hide()
if self.settings.value("sys_tray", True, bool): e.ignore()
self.hide() return
e.ignore() self.timer.stop()
return self.tray_icon.deleteLater()
elif self.args.offline: self.hide()
pass self.exit_app.emit(self.exit_code)
self.signals.exit_app.emit(0) super(MainWindow, self).closeEvent(e)
e.ignore() e.accept()

View file

@ -13,7 +13,7 @@ logger = getLogger("TrayIcon")
class TrayIcon(QSystemTrayIcon): class TrayIcon(QSystemTrayIcon):
def __init__(self, parent): def __init__(self, parent):
super(TrayIcon, self).__init__(parent) super(TrayIcon, self).__init__(parent=parent)
self.core = LegendaryCoreSingleton() self.core = LegendaryCoreSingleton()
self.setIcon(QIcon(":/images/Rare.png")) self.setIcon(QIcon(":/images/Rare.png"))
@ -33,7 +33,7 @@ class TrayIcon(QSystemTrayIcon):
if len(installed := self.core.get_installed_list()) < 5: if len(installed := self.core.get_installed_list()) < 5:
last_played = [GameMeta(i.app_name) for i in sorted(installed, key=lambda x: x.title)] last_played = [GameMeta(i.app_name) for i in sorted(installed, key=lambda x: x.title)]
elif games := sorted( elif games := sorted(
parent.mainwindow.tab_widget.games_tab.game_utils.game_meta.get_games(), parent.tab_widget.games_tab.game_utils.game_meta.get_games(),
key=lambda x: x.last_played, reverse=True): key=lambda x: x.last_played, reverse=True):
last_played: List[GameMeta] = games[0:5] last_played: List[GameMeta] = games[0:5]
else: else:
@ -46,7 +46,7 @@ class TrayIcon(QSystemTrayIcon):
a.setProperty("app_name", game.app_name) a.setProperty("app_name", game.app_name)
self.game_actions.append(a) self.game_actions.append(a)
a.triggered.connect( a.triggered.connect(
lambda: parent.mainwindow.tab_widget.games_tab.game_utils.prepare_launch( lambda: parent.tab_widget.games_tab.game_utils.prepare_launch(
self.sender().property("app_name")) self.sender().property("app_name"))
) )

View file

@ -5,14 +5,18 @@ Each of the objects in this module should be instantiated ONCE
and only ONCE! and only ONCE!
""" """
import configparser
import logging
import os
from argparse import Namespace from argparse import Namespace
from typing import Optional from typing import Optional, Union
from rare.lgndr.core import LegendaryCore from rare.lgndr.core import LegendaryCore
from rare.models.apiresults import ApiResults from rare.models.apiresults import ApiResults
from rare.models.signals import GlobalSignals from rare.models.signals import GlobalSignals
logger = logging.getLogger("Singleton")
_legendary_core_singleton: Optional[LegendaryCore] = None _legendary_core_singleton: Optional[LegendaryCore] = None
_global_signals_singleton: Optional[GlobalSignals] = None _global_signals_singleton: Optional[GlobalSignals] = None
_arguments_singleton: Optional[Namespace] = None _arguments_singleton: Optional[Namespace] = None
@ -23,8 +27,33 @@ def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore:
global _legendary_core_singleton global _legendary_core_singleton
if _legendary_core_singleton is None and not init: if _legendary_core_singleton is None and not init:
raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") raise RuntimeError("Uninitialized use of LegendaryCoreSingleton")
if _legendary_core_singleton is None: if _legendary_core_singleton is not None and init:
_legendary_core_singleton = LegendaryCore() raise RuntimeError("LegendaryCore already initialized")
if init:
try:
_legendary_core_singleton = LegendaryCore()
except configparser.MissingSectionHeaderError as e:
logger.warning(f"Config is corrupt: {e}")
if config_path := os.environ.get("XDG_CONFIG_HOME"):
path = os.path.join(config_path, "legendary")
else:
path = os.path.expanduser("~/.config/legendary")
with open(os.path.join(path, "config.ini"), "w") as config_file:
config_file.write("[Legendary]")
_legendary_core_singleton = LegendaryCore()
if "Legendary" not in _legendary_core_singleton.lgd.config.sections():
_legendary_core_singleton.lgd.config.add_section("Legendary")
_legendary_core_singleton.lgd.save_config()
# workaround if egl sync enabled, but no programdata_path
# programdata_path might be unset if logging in through the browser
if _legendary_core_singleton.egl_sync_enabled:
if _legendary_core_singleton.egl.programdata_path is None:
_legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync")
_legendary_core_singleton.lgd.save_config()
else:
if not os.path.exists(_legendary_core_singleton.egl.programdata_path):
_legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync")
_legendary_core_singleton.lgd.save_config()
return _legendary_core_singleton return _legendary_core_singleton
@ -32,7 +61,9 @@ def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals:
global _global_signals_singleton global _global_signals_singleton
if _global_signals_singleton is None and not init: if _global_signals_singleton is None and not init:
raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") raise RuntimeError("Uninitialized use of GlobalSignalsSingleton")
if _global_signals_singleton is None: if _global_signals_singleton is not None and init:
raise RuntimeError("GlobalSignals already initialized")
if init:
_global_signals_singleton = GlobalSignals() _global_signals_singleton = GlobalSignals()
return _global_signals_singleton return _global_signals_singleton
@ -41,7 +72,9 @@ def ArgumentsSingleton(args: Namespace = None) -> Optional[Namespace]:
global _arguments_singleton global _arguments_singleton
if _arguments_singleton is None and args is None: if _arguments_singleton is None and args is None:
raise RuntimeError("Uninitialized use of ArgumentsSingleton") raise RuntimeError("Uninitialized use of ArgumentsSingleton")
if _arguments_singleton is None: if _arguments_singleton is not None and args is not None:
raise RuntimeError("Arguments already initialized")
if args is not None:
_arguments_singleton = args _arguments_singleton = args
return _arguments_singleton return _arguments_singleton
@ -50,7 +83,27 @@ def ApiResultsSingleton(res: ApiResults = None) -> Optional[ApiResults]:
global _api_results_singleton global _api_results_singleton
if _api_results_singleton is None and res is None: if _api_results_singleton is None and res is None:
raise RuntimeError("Uninitialized use of ApiResultsSingleton") raise RuntimeError("Uninitialized use of ApiResultsSingleton")
if _api_results_singleton is None: if _api_results_singleton is not None and res is not None:
raise RuntimeError("ApiResults already initialized")
if res is not None:
_api_results_singleton = res _api_results_singleton = res
return _api_results_singleton return _api_results_singleton
def clear_singleton_instance(instance: Union[LegendaryCore, GlobalSignals, Namespace, ApiResults]):
global _legendary_core_singleton, _global_signals_singleton, _arguments_singleton, _api_results_singleton
if isinstance(instance, LegendaryCore):
del instance
_legendary_core_singleton = None
elif isinstance(instance, GlobalSignals):
instance.deleteLater()
del instance
_global_signals_singleton = None
elif isinstance(instance, Namespace):
del instance
_arguments_singleton = None
elif isinstance(instance, ApiResults):
del instance
_api_results_singleton = None
else:
raise RuntimeError(f"Instance is of unknown type \"{type(instance)}\"")

View file

@ -54,7 +54,6 @@ class IndicatorReasons:
class IndicatorLineEdit(QWidget): class IndicatorLineEdit(QWidget):
textChanged = pyqtSignal(str) textChanged = pyqtSignal(str)
is_valid = False
reasons = IndicatorReasons() reasons = IndicatorReasons()
def __init__( def __init__(
@ -97,9 +96,10 @@ class IndicatorLineEdit(QWidget):
layout.addWidget(self.indicator_label) layout.addWidget(self.indicator_label)
if not placeholder: if not placeholder:
_translate = QCoreApplication.translate _translate = QCoreApplication.instance().translate
self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default")) self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default"))
self.is_valid = False
self.edit_func = edit_func self.edit_func = edit_func
self.save_func = save_func self.save_func = save_func
self.line_edit.textChanged.connect(self.__edit) self.line_edit.textChanged.connect(self.__edit)
@ -107,7 +107,7 @@ class IndicatorLineEdit(QWidget):
self.line_edit.textChanged.connect(self.__save) self.line_edit.textChanged.connect(self.__save)
# lk: this can be placed here to trigger __edit # lk: this can be placed here to trigger __edit
# lk: it going to save the input again if it is valid which # lk: it is going to save the input again if it is valid which
# lk: is ok to do given the checks don't misbehave (they shouldn't) # lk: is ok to do given the checks don't misbehave (they shouldn't)
# lk: however it is going to edit any "understood" bad input to good input # lk: however it is going to edit any "understood" bad input to good input
# lk: and we might not want that (but the validity check reports on the edited string) # lk: and we might not want that (but the validity check reports on the edited string)
@ -185,9 +185,6 @@ class PathEditIconProvider(QFileIconProvider):
class PathEdit(IndicatorLineEdit): class PathEdit(IndicatorLineEdit):
completer = QCompleter()
compl_model = QFileSystemModel()
def __init__( def __init__(
self, self,
path: str = "", path: str = "",
@ -200,6 +197,9 @@ class PathEdit(IndicatorLineEdit):
horiz_policy: QSizePolicy = QSizePolicy.Expanding, horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None, parent=None,
): ):
self.completer = QCompleter()
self.compl_model = QFileSystemModel()
try: try:
self.compl_model.setOptions( self.compl_model.setOptions(
QFileSystemModel.DontWatchForChanges QFileSystemModel.DontWatchForChanges
@ -230,7 +230,7 @@ class PathEdit(IndicatorLineEdit):
layout = self.layout() layout = self.layout()
layout.addWidget(self.path_select) layout.addWidget(self.path_select)
_translate = QCoreApplication.translate _translate = QCoreApplication.instance().translate
self.path_select.setText(_translate("PathEdit", "Browse...")) self.path_select.setText(_translate("PathEdit", "Browse..."))
self.type_filter = type_filter self.type_filter = type_filter
@ -414,7 +414,7 @@ class SelectViewWidget(QWidget):
class ImageLabel(QLabel): class ImageLabel(QLabel):
image = None image = None
img_size = None img_size = None
name = str() name = ""
def __init__(self): def __init__(self):
super(ImageLabel, self).__init__() super(ImageLabel, self).__init__()