Merge pull request #1096 from pypeclub/feature/386-show-diagnostic-info-when-clicking-on-version-in-pype-tray

Show diagnostic info when clicking on version in pype tray
This commit is contained in:
Milan Kolar 2021-03-10 17:29:59 +01:00 committed by GitHub
commit 8780c51f1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 553 additions and 131 deletions

87
pype/lib/pype_info.py Normal file
View file

@ -0,0 +1,87 @@
import os
import json
import datetime
import platform
import getpass
import socket
import pype.version
from pype.settings.lib import get_local_settings
from .execute import get_pype_execute_args
from .local_settings import get_local_site_id
def get_pype_version():
"""Version of pype that is currently used."""
return pype.version.__version__
def get_pype_info():
"""Information about currently used Pype process."""
executable_args = get_pype_execute_args()
if len(executable_args) == 1:
version_type = "build"
else:
version_type = "code"
return {
"version": get_pype_version(),
"version_type": version_type,
"executable": executable_args[-1],
"pype_root": os.environ["PYPE_ROOT"],
"mongo_url": os.environ["PYPE_MONGO"]
}
def get_workstation_info():
"""Basic information about workstation."""
host_name = socket.gethostname()
try:
host_ip = socket.gethostbyname(host_name)
except socket.gaierror:
host_ip = "127.0.0.1"
return {
"hostname": host_name,
"hostip": host_ip,
"username": getpass.getuser(),
"system_name": platform.system(),
"local_id": get_local_site_id()
}
def get_all_current_info():
"""All information about current process in one dictionary."""
return {
"pype": get_pype_info(),
"workstation": get_workstation_info(),
"env": os.environ.copy(),
"local_settings": get_local_settings()
}
def extract_pype_info_to_file(dirpath):
"""Extract all current info to a file.
It is possible to define onpy directory path. Filename is concatenated with
pype version, workstation site id and timestamp.
Args:
dirpath (str): Path to directory where file will be stored.
Returns:
filepath (str): Full path to file where data were extracted.
"""
filename = "{}_{}_{}.json".format(
get_pype_version(),
get_local_site_id(),
datetime.datetime.now().strftime("%y%m%d%H%M%S")
)
filepath = os.path.join(dirpath, filename)
data = get_all_current_info()
if not os.path.exists(dirpath):
os.makedirs(dirpath)
with open(filepath, "w") as file_stream:
json.dump(data, file_stream, indent=4)
return filepath

View file

@ -108,6 +108,7 @@ class ITrayModule:
would do nothing.
"""
tray_initialized = False
_tray_manager = None
@abstractmethod
def tray_init(self):
@ -138,6 +139,20 @@ class ITrayModule:
"""
pass
def show_tray_message(self, title, message, icon=None, msecs=None):
"""Show tray message.
Args:
title (str): Title of message.
message (str): Content of message.
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
Information icon, may differ by Qt version.
msecs (int): Duration of message visibility in miliseconds.
Default is 10000 msecs, may differ by Qt version.
"""
if self._tray_manager:
self._tray_manager.show_tray_message(title, message, icon, msecs)
class ITrayAction(ITrayModule):
"""Implementation of Tray action.
@ -638,8 +653,10 @@ class TrayModulesManager(ModulesManager):
self.modules_by_id = {}
self.modules_by_name = {}
self._report = {}
self.tray_manager = None
def initialize(self, tray_menu):
def initialize(self, tray_manager, tray_menu):
self.tray_manager = tray_manager
self.initialize_modules()
self.tray_init()
self.connect_modules()
@ -658,6 +675,7 @@ class TrayModulesManager(ModulesManager):
prev_start_time = time_start
for module in self.get_enabled_tray_modules():
try:
module._tray_manager = self.tray_manager
module.tray_init()
module.tray_initialized = True
except Exception:

View file

@ -1,17 +0,0 @@
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="312px" height="312px" viewBox="0 0 40 40" xml:space="preserve">
<path opacity="0.2" fill="#ffa500" d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z"/>
<path fill="#ffa500" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0
C22.32,8.481,24.301,9.057,26.013,10.047z">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="00 20.2 20.1"
to="360 20.2 20.1"
dur="0.5s"
repeatCount="indefinite"/>
</path>
<text x="3" y="23" fill="#ffa500" font-style="bold" font-size="7px" font-family="sans-serif">Working...</text>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,402 @@
import os
import json
import collections
from avalon import style
from Qt import QtCore, QtGui, QtWidgets
from pype.api import resources
from pype.settings.lib import get_local_settings
from pype.lib.pype_info import (
get_all_current_info,
get_pype_info,
get_workstation_info,
extract_pype_info_to_file
)
IS_MAIN_ROLE = QtCore.Qt.UserRole
class EnvironmentValueDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
edit_widget = QtWidgets.QLineEdit(parent)
edit_widget.setReadOnly(True)
return edit_widget
class EnvironmentsView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(EnvironmentsView, self).__init__(parent)
model = QtGui.QStandardItemModel()
env = os.environ.copy()
keys = []
values = []
for key in sorted(env.keys()):
key_item = QtGui.QStandardItem(key)
key_item.setFlags(
QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsEnabled
)
key_item.setData(True, IS_MAIN_ROLE)
keys.append(key_item)
value = env[key]
value_item = QtGui.QStandardItem(value)
value_item.setData(True, IS_MAIN_ROLE)
values.append(value_item)
value_parts = [
part
for part in value.split(os.pathsep) if part
]
if len(value_parts) < 2:
continue
sub_parts = []
for part_value in value_parts:
part_item = QtGui.QStandardItem(part_value)
part_item.setData(False, IS_MAIN_ROLE)
sub_parts.append(part_item)
key_item.appendRows(sub_parts)
model.appendColumn(keys)
model.appendColumn(values)
model.setHorizontalHeaderLabels(["Key", "Value"])
self.setModel(model)
# self.setIndentation(0)
delegate = EnvironmentValueDelegate(self)
self.setItemDelegate(delegate)
self.header().setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents
)
self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
def get_selection_as_dict(self):
indexes = self.selectionModel().selectedIndexes()
main_mapping = collections.defaultdict(dict)
for index in indexes:
is_main = index.data(IS_MAIN_ROLE)
if not is_main:
continue
row = index.row()
value = index.data(QtCore.Qt.DisplayRole)
if index.column() == 0:
key = "key"
else:
key = "value"
main_mapping[row][key] = value
result = {}
for item in main_mapping.values():
result[item["key"]] = item["value"]
return result
def keyPressEvent(self, event):
if (
event.type() == QtGui.QKeyEvent.KeyPress
and event.matches(QtGui.QKeySequence.Copy)
):
selected_data = self.get_selection_as_dict()
selected_str = json.dumps(selected_data, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(selected_str)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
event.accept()
else:
return super(EnvironmentsView, self).keyPressEvent(event)
class ClickableWidget(QtWidgets.QWidget):
clicked = QtCore.Signal()
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.clicked.emit()
super(ClickableWidget, self).mouseReleaseEvent(event)
class CollapsibleWidget(QtWidgets.QWidget):
def __init__(self, label, parent):
super(CollapsibleWidget, self).__init__(parent)
self.content_widget = None
top_part = ClickableWidget(parent=self)
button_size = QtCore.QSize(5, 5)
button_toggle = QtWidgets.QToolButton(parent=top_part)
button_toggle.setIconSize(button_size)
button_toggle.setArrowType(QtCore.Qt.RightArrow)
button_toggle.setCheckable(True)
button_toggle.setChecked(False)
label_widget = QtWidgets.QLabel(label, parent=top_part)
spacer_widget = QtWidgets.QWidget(top_part)
top_part_layout = QtWidgets.QHBoxLayout(top_part)
top_part_layout.setContentsMargins(0, 0, 0, 5)
top_part_layout.addWidget(button_toggle)
top_part_layout.addWidget(label_widget)
top_part_layout.addWidget(spacer_widget, 1)
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.button_toggle = button_toggle
self.label_widget = label_widget
top_part.clicked.connect(self._top_part_clicked)
self.button_toggle.clicked.connect(self._btn_clicked)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.setAlignment(QtCore.Qt.AlignTop)
main_layout.addWidget(top_part)
self.main_layout = main_layout
def set_content_widget(self, content_widget):
content_widget.setVisible(self.button_toggle.isChecked())
self.main_layout.addWidget(content_widget)
self.content_widget = content_widget
def _btn_clicked(self):
self.toggle_content(self.button_toggle.isChecked())
def _top_part_clicked(self):
self.toggle_content()
def toggle_content(self, *args):
if len(args) > 0:
checked = args[0]
else:
checked = not self.button_toggle.isChecked()
arrow_type = QtCore.Qt.RightArrow
if checked:
arrow_type = QtCore.Qt.DownArrow
self.button_toggle.setChecked(checked)
self.button_toggle.setArrowType(arrow_type)
if self.content_widget:
self.content_widget.setVisible(checked)
self.parent().updateGeometry()
def resizeEvent(self, event):
super(CollapsibleWidget, self).resizeEvent(event)
if self.content_widget:
self.content_widget.updateGeometry()
class PypeInfoWidget(QtWidgets.QWidget):
not_applicable = "N/A"
def __init__(self, parent=None):
super(PypeInfoWidget, self).__init__(parent)
self.setStyleSheet(style.load_stylesheet())
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowTitle("Pype info")
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setAlignment(QtCore.Qt.AlignTop)
main_layout.addWidget(self._create_pype_info_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_workstation_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_local_settings_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_environ_widget(), 1)
main_layout.addWidget(self._create_btns_section(), 0)
def _create_btns_section(self):
btns_widget = QtWidgets.QWidget(self)
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btns_layout.setContentsMargins(0, 0, 0, 0)
copy_to_clipboard_btn = QtWidgets.QPushButton(
"Copy to clipboard", btns_widget
)
export_to_file_btn = QtWidgets.QPushButton(
"Export", btns_widget
)
btns_layout.addWidget(QtWidgets.QWidget(btns_widget), 1)
btns_layout.addWidget(copy_to_clipboard_btn)
btns_layout.addWidget(export_to_file_btn)
copy_to_clipboard_btn.clicked.connect(self._on_copy_to_clipboard)
export_to_file_btn.clicked.connect(self._on_export_to_file)
return btns_widget
def _on_export_to_file(self):
dst_dir_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
"Choose directory",
os.path.expanduser("~"),
QtWidgets.QFileDialog.ShowDirsOnly
)
if not dst_dir_path or not os.path.exists(dst_dir_path):
return
filepath = extract_pype_info_to_file(dst_dir_path)
title = "Extraction done"
message = "Extraction is done. Destination filepath is \"{}\"".format(
filepath.replace("\\", "/")
)
dialog = QtWidgets.QMessageBox(self)
dialog.setIcon(QtWidgets.QMessageBox.NoIcon)
dialog.setWindowTitle(title)
dialog.setText(message)
dialog.exec_()
def _on_copy_to_clipboard(self):
all_data = get_all_current_info()
all_data_str = json.dumps(all_data, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(all_data_str)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
def _create_separator(self):
separator_widget = QtWidgets.QWidget(self)
separator_widget.setStyleSheet("background: #222222;")
separator_widget.setMinimumHeight(2)
separator_widget.setMaximumHeight(2)
return separator_widget
def _create_workstation_widget(self):
key_label_mapping = {
"system_name": "System:",
"local_id": "Local ID:",
"username": "Username:",
"hostname": "Hostname:",
"hostip": "Host IP:"
}
keys_order = [
"system_name",
"local_id",
"username",
"hostname",
"hostip"
]
workstation_info = get_workstation_info()
for key in workstation_info.keys():
if key not in keys_order:
keys_order.append(key)
wokstation_info_widget = CollapsibleWidget("Workstation info", self)
info_widget = QtWidgets.QWidget(self)
info_layout = QtWidgets.QGridLayout(info_widget)
# Add spacer to 3rd column
info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2)
info_layout.setColumnStretch(2, 1)
for key in keys_order:
if key not in workstation_info:
continue
label = key_label_mapping.get(key, key)
value = workstation_info[key]
row = info_layout.rowCount()
info_layout.addWidget(
QtWidgets.QLabel(label), row, 0, 1, 1
)
value_label = QtWidgets.QLabel(value)
value_label.setTextInteractionFlags(
QtCore.Qt.TextSelectableByMouse
)
info_layout.addWidget(
value_label, row, 1, 1, 1
)
wokstation_info_widget.set_content_widget(info_widget)
return wokstation_info_widget
def _create_local_settings_widget(self):
local_settings = get_local_settings()
local_settings_widget = CollapsibleWidget("Local settings", self)
settings_input = QtWidgets.QPlainTextEdit(local_settings_widget)
settings_input.setReadOnly(True)
settings_input.setPlainText(json.dumps(local_settings, indent=4))
local_settings_widget.set_content_widget(settings_input)
return local_settings_widget
def _create_environ_widget(self):
env_widget = CollapsibleWidget("Environments", self)
env_view = EnvironmentsView(env_widget)
env_widget.set_content_widget(env_view)
return env_widget
def _create_pype_info_widget(self):
"""Create widget with information about pype application."""
# Get pype info data
pype_info = get_pype_info()
# Modify version key/values
version_value = "{} ({})".format(
pype_info.pop("version", self.not_applicable),
pype_info.pop("version_type", self.not_applicable)
)
pype_info["version_value"] = version_value
# Prepare lable mapping
key_label_mapping = {
"version_value": "Pype version:",
"executable": "Pype executable:",
"pype_root": "Pype location:",
"mongo_url": "Pype Mongo URL:"
}
# Prepare keys order
keys_order = ["version_value", "executable", "pype_root", "mongo_url"]
for key in pype_info.keys():
if key not in keys_order:
keys_order.append(key)
# Create widgets
info_widget = QtWidgets.QWidget(self)
info_layout = QtWidgets.QGridLayout(info_widget)
# Add spacer to 3rd column
info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2)
info_layout.setColumnStretch(2, 1)
title_label = QtWidgets.QLabel(info_widget)
title_label.setText("Application information")
title_label.setStyleSheet("font-weight: bold;")
info_layout.addWidget(title_label, 0, 0, 1, 2)
for key in keys_order:
if key not in pype_info:
continue
value = pype_info[key]
label = key_label_mapping.get(key, key)
row = info_layout.rowCount()
info_layout.addWidget(
QtWidgets.QLabel(label), row, 0, 1, 1
)
value_label = QtWidgets.QLabel(value)
value_label.setTextInteractionFlags(
QtCore.Qt.TextSelectableByMouse
)
info_layout.addWidget(
value_label, row, 1, 1, 1
)
return info_widget

View file

@ -3,15 +3,12 @@ import sys
import platform
from avalon import style
from Qt import QtCore, QtGui, QtWidgets, QtSvg
from Qt import QtCore, QtGui, QtWidgets
from pype.api import Logger, resources
from pype.modules import TrayModulesManager, ITrayService
from pype.settings.lib import get_system_settings
import pype.version
try:
import configparser
except Exception:
import ConfigParser as configparser
from .pype_info_widget import PypeInfoWidget
class TrayManager:
@ -19,13 +16,14 @@ class TrayManager:
Load submenus, actions, separators and modules into tray's context.
"""
available_sourcetypes = ["python", "file"]
def __init__(self, tray_widget, main_window):
self.tray_widget = tray_widget
self.main_window = main_window
self.log = Logger().get_logger(self.__class__.__name__)
self.pype_info_widget = None
self.log = Logger.get_logger(self.__class__.__name__)
self.module_settings = get_system_settings()["modules"]
@ -36,7 +34,7 @@ class TrayManager:
def initialize_modules(self):
"""Add modules to tray."""
self.modules_manager.initialize(self.tray_widget.menu)
self.modules_manager.initialize(self, self.tray_widget.menu)
# Add services if they are
services_submenu = ITrayService.services_submenu(self.tray_widget.menu)
@ -58,6 +56,26 @@ class TrayManager:
# Print time report
self.modules_manager.print_report()
def show_tray_message(self, title, message, icon=None, msecs=None):
"""Show tray message.
Args:
title (str): Title of message.
message (str): Content of message.
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
Information icon, may differ by Qt version.
msecs (int): Duration of message visibility in miliseconds.
Default is 10000 msecs, may differ by Qt version.
"""
args = [title, message]
kwargs = {}
if icon:
kwargs["icon"] = icon
if msecs:
kwargs["msecs"] = msecs
self.tray_widget.showMessage(*args, **kwargs)
def _add_version_item(self):
subversion = os.environ.get("PYPE_SUBVERSION")
client_name = os.environ.get("PYPE_CLIENT")
@ -70,12 +88,21 @@ class TrayManager:
version_string += ", {}".format(client_name)
version_action = QtWidgets.QAction(version_string, self.tray_widget)
version_action.triggered.connect(self._on_version_action)
self.tray_widget.menu.addAction(version_action)
self.tray_widget.menu.addSeparator()
def on_exit(self):
self.modules_manager.on_exit()
def _on_version_action(self):
if self.pype_info_widget is None:
self.pype_info_widget = PypeInfoWidget()
self.pype_info_widget.show()
self.pype_info_widget.raise_()
self.pype_info_widget.activateWindow()
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
"""Tray widget.
@ -85,9 +112,9 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
"""
def __init__(self, parent):
self.icon = QtGui.QIcon(resources.pype_icon_filepath())
icon = QtGui.QIcon(resources.pype_icon_filepath())
QtWidgets.QSystemTrayIcon.__init__(self, self.icon, parent)
super(SystemTrayIcon, self).__init__(icon, parent)
# Store parent - QtWidgets.QMainWindow()
self.parent = parent
@ -100,15 +127,15 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
self.tray_man = TrayManager(self, self.parent)
self.tray_man.initialize_modules()
# Catch activate event
self.activated.connect(self.on_systray_activated)
# Catch activate event for left click if not on MacOS
# - MacOS has this ability by design so menu would be doubled
if platform.system().lower() != "darwin":
self.activated.connect(self.on_systray_activated)
# Add menu to Context of SystemTrayIcon
self.setContextMenu(self.menu)
def on_systray_activated(self, reason):
# show contextMenu if left click
if platform.system().lower() == "darwin":
return
if reason == QtWidgets.QSystemTrayIcon.Trigger:
position = QtGui.QCursor().pos()
self.contextMenu().popup(position)
@ -128,119 +155,24 @@ class TrayMainWindow(QtWidgets.QMainWindow):
Every widget should have set this window as parent because
QSystemTrayIcon widget is not allowed to be a parent of any widget.
:param app: Qt application manages application's control flow
:type app: QtWidgets.QApplication
.. note::
*TrayMainWindow* has ability to show **working** widget.
Calling methods:
- ``show_working()``
- ``hide_working()``
.. todo:: Hide working widget if idle is too long
"""
def __init__(self, app):
super().__init__()
super(TrayMainWindow, self).__init__()
self.app = app
self.set_working_widget()
self.trayIcon = SystemTrayIcon(self)
self.trayIcon.show()
def set_working_widget(self):
image_file = resources.get_resource("icons", "working.svg")
img_pix = QtGui.QPixmap(image_file)
if image_file.endswith('.svg'):
widget = QtSvg.QSvgWidget(image_file)
else:
widget = QtWidgets.QLabel()
widget.setPixmap(img_pix)
# Set widget properties
widget.setGeometry(img_pix.rect())
widget.setMask(img_pix.mask())
widget.setWindowFlags(
QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint
)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.center_widget(widget)
self._working_widget = widget
self.helper = DragAndDropHelper(self._working_widget)
def center_widget(self, widget):
frame_geo = widget.frameGeometry()
screen = self.app.desktop().cursor().pos()
center_point = self.app.desktop().screenGeometry(
self.app.desktop().screenNumber(screen)
).center()
frame_geo.moveCenter(center_point)
widget.move(frame_geo.topLeft())
def show_working(self):
self._working_widget.show()
def hide_working(self):
self.center_widget(self._working_widget)
self._working_widget.hide()
class DragAndDropHelper:
""" Helper adds to widget drag and drop ability
:param widget: Qt Widget where drag and drop ability will be added
"""
def __init__(self, widget):
self.widget = widget
self.widget.mousePressEvent = self.mousePressEvent
self.widget.mouseMoveEvent = self.mouseMoveEvent
self.widget.mouseReleaseEvent = self.mouseReleaseEvent
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.widget.mapToGlobal(
self.widget.pos()
)
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.widget.mapFromGlobal(currPos + diff)
self.widget.move(newPos)
self.__mouseMovePos = globalPos
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
self.tray_widget = SystemTrayIcon(self)
self.tray_widget.show()
class PypeTrayApplication(QtWidgets.QApplication):
"""Qt application manages application's control flow."""
def __init__(self):
super(self.__class__, self).__init__(sys.argv)
super(PypeTrayApplication, self).__init__(sys.argv)
# Allows to close widgets without exiting app
self.setQuitOnLastWindowClosed(False)
# Allow show icon istead of python icon in task bar (Windows)
if os.name == "nt":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
u"pype_tray"
)
# Sets up splash
splash_widget = self.set_splash()