Merge pull request #1550 from pypeclub/feature/910-ask-user-to-restart-after-changing-global-environments-in-settings

This commit is contained in:
Milan Kolar 2021-05-27 21:05:50 +02:00 committed by GitHub
commit 194c81b0dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 217 additions and 8 deletions

View file

@ -680,6 +680,10 @@ class TrayModulesManager(ModulesManager):
output.append(module)
return output
def restart_tray(self):
if self.tray_manager:
self.tray_manager.restart()
def tray_init(self):
report = {}
time_start = time.time()

View file

@ -67,6 +67,10 @@ class SettingsAction(PypeModule, ITrayAction):
return
from openpype.tools.settings import MainWidget
self.settings_window = MainWidget(self.user_role)
self.settings_window.trigger_restart.connect(self._on_trigger_restart)
def _on_trigger_restart(self):
self.manager.restart_tray()
def show_settings_window(self):
"""Show settings tool window.

View file

@ -111,6 +111,8 @@ class BaseItemEntity(BaseEntity):
self.file_item = None
# Reference to `RootEntity`
self.root_item = None
# Change of value requires restart of OpenPype
self._require_restart_on_change = False
# Entity is in hierarchy of dynamically created entity
self.is_in_dynamic_item = False
@ -171,6 +173,14 @@ class BaseItemEntity(BaseEntity):
roles = [roles]
self.roles = roles
@property
def require_restart_on_change(self):
return self._require_restart_on_change
@property
def require_restart(self):
return False
@property
def has_studio_override(self):
"""Says if entity or it's children has studio overrides."""
@ -261,6 +271,14 @@ class BaseItemEntity(BaseEntity):
self, "Dynamic entity has set `is_group` to true."
)
if (
self.require_restart_on_change
and (self.is_dynamic_item or self.is_in_dynamic_item)
):
raise EntitySchemaError(
self, "Dynamic entity can't require restart."
)
@abstractmethod
def set_override_state(self, state):
"""Set override state and trigger it on children.
@ -788,6 +806,15 @@ class ItemEntity(BaseItemEntity):
# Root item reference
self.root_item = self.parent.root_item
# Item require restart on value change
require_restart_on_change = self.schema_data.get("require_restart")
if (
require_restart_on_change is None
and not (self.is_dynamic_item or self.is_in_dynamic_item)
):
require_restart_on_change = self.parent.require_restart_on_change
self._require_restart_on_change = require_restart_on_change
# File item reference
if self.parent.is_file:
self.file_item = self.parent

View file

@ -68,8 +68,18 @@ class EndpointEntity(ItemEntity):
def on_change(self):
for callback in self.on_change_callbacks:
callback()
if self.require_restart_on_change:
if self.require_restart:
self.root_item.add_item_require_restart(self)
else:
self.root_item.remove_item_require_restart(self)
self.parent.on_child_change(self)
@property
def require_restart(self):
return self.has_unsaved_changes
def update_default_value(self, value):
value = self._check_update_value(value, "default")
self._default_value = value
@ -115,6 +125,10 @@ class InputEntity(EndpointEntity):
"""Entity's value without metadata."""
return self._current_value
@property
def require_restart(self):
return self._value_is_modified
def _settings_value(self):
return copy.deepcopy(self.value)

View file

@ -55,6 +55,8 @@ class RootEntity(BaseItemEntity):
def __init__(self, schema_data, reset):
super(RootEntity, self).__init__(schema_data)
self._require_restart_callbacks = []
self._item_ids_require_restart = set()
self._item_initalization()
if reset:
self.reset()
@ -64,6 +66,31 @@ class RootEntity(BaseItemEntity):
"""Current OverrideState."""
return self._override_state
@property
def require_restart(self):
return bool(self._item_ids_require_restart)
def add_require_restart_change_callback(self, callback):
self._require_restart_callbacks.append(callback)
def _on_require_restart_change(self):
for callback in self._require_restart_callbacks:
callback()
def add_item_require_restart(self, item):
was_empty = len(self._item_ids_require_restart) == 0
self._item_ids_require_restart.add(item.id)
if was_empty:
self._on_require_restart_change()
def remove_item_require_restart(self, item):
if item.id not in self._item_ids_require_restart:
return
self._item_ids_require_restart.remove(item.id)
if not self._item_ids_require_restart:
self._on_require_restart_change()
@abstractmethod
def reset(self):
"""Reset values and entities to initial state.

View file

@ -3,6 +3,7 @@
"key": "ftrack",
"label": "Ftrack",
"collapsible": true,
"require_restart": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -34,7 +34,8 @@
"key": "environment",
"label": "Environment",
"type": "raw-json",
"env_group_key": "global"
"env_group_key": "global",
"require_restart": true
},
{
"type": "splitter"
@ -44,7 +45,8 @@
"key": "openpype_path",
"label": "Versions Repository",
"multiplatform": true,
"multipath": true
"multipath": true,
"require_restart": true
}
]
}

View file

@ -10,6 +10,7 @@
"key": "avalon",
"label": "Avalon",
"collapsible": true,
"require_restart": true,
"children": [
{
"type": "number",
@ -35,6 +36,7 @@
"key": "timers_manager",
"label": "Timers Manager",
"collapsible": true,
"require_restart": true,
"checkbox_key": "enabled",
"children": [
{
@ -66,6 +68,7 @@
"key": "clockify",
"label": "Clockify",
"collapsible": true,
"require_restart": true,
"checkbox_key": "enabled",
"children": [
{
@ -84,6 +87,7 @@
"key": "sync_server",
"label": "Site Sync",
"collapsible": true,
"require_restart": true,
"checkbox_key": "enabled",
"children": [
{
@ -114,6 +118,7 @@
"type": "dict",
"key": "deadline",
"label": "Deadline",
"require_restart": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
@ -133,6 +138,7 @@
"type": "dict",
"key": "muster",
"label": "Muster",
"require_restart": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [

View file

@ -73,6 +73,7 @@ class IgnoreInputChangesObj:
class SettingsCategoryWidget(QtWidgets.QWidget):
state_changed = QtCore.Signal()
saved = QtCore.Signal(QtWidgets.QWidget)
restart_required_trigger = QtCore.Signal()
def __init__(self, user_role, parent=None):
super(SettingsCategoryWidget, self).__init__(parent)
@ -185,9 +186,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
if self.user_role == "developer":
self._add_developer_ui(footer_layout)
save_btn = QtWidgets.QPushButton("Save")
spacer_widget = QtWidgets.QWidget()
footer_layout.addWidget(spacer_widget, 1)
save_btn = QtWidgets.QPushButton("Save", footer_widget)
require_restart_label = QtWidgets.QLabel(footer_widget)
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
footer_layout.addWidget(require_restart_label, 1)
footer_layout.addWidget(save_btn, 0)
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
@ -205,6 +207,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
save_btn.clicked.connect(self._save)
self.save_btn = save_btn
self.require_restart_label = require_restart_label
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
@ -323,6 +326,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
def _on_reset_start(self):
return
def _on_require_restart_change(self):
value = ""
if self.entity.require_restart:
value = (
"Your changes require restart of"
" all running OpenPype processes to take affect."
)
self.require_restart_label.setText(value)
def reset(self):
self.set_state(CategoryState.Working)
@ -339,6 +351,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
dialog = None
try:
self._create_root_entity()
self.entity.add_require_restart_change_callback(
self._on_require_restart_change
)
self.add_children_gui()
@ -433,6 +448,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
return
def _save(self):
# Don't trigger restart if defaults are modified
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
require_restart = False
else:
require_restart = self.entity.require_restart
self.set_state(CategoryState.Working)
if self.items_are_valid():
@ -442,6 +466,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self.saved.emit(self)
if require_restart:
self.restart_required_trigger.emit()
self.require_restart_label.setText("")
def _on_refresh(self):
self.reset()

View file

@ -275,8 +275,6 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
layout.addWidget(message_label)
layout.addWidget(btns_widget)
self.state = None
def on_cancel_pressed(self):
self.done(0)
@ -287,6 +285,48 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
self.done(2)
class RestartDialog(QtWidgets.QDialog):
message = (
"Your changes require restart of process to take effect."
" Do you want to restart now?"
)
def __init__(self, parent=None):
super(RestartDialog, self).__init__(parent)
message_label = QtWidgets.QLabel(self.message)
btns_widget = QtWidgets.QWidget(self)
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btn_restart = QtWidgets.QPushButton("Restart")
btn_restart.clicked.connect(self.on_restart_pressed)
btn_cancel = QtWidgets.QPushButton("Cancel")
btn_cancel.clicked.connect(self.on_cancel_pressed)
btns_layout.addStretch(1)
btns_layout.addWidget(btn_restart)
btns_layout.addWidget(btn_cancel)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(message_label)
layout.addWidget(btns_widget)
self.btn_cancel = btn_cancel
self.btn_restart = btn_restart
def showEvent(self, event):
super(RestartDialog, self).showEvent(event)
btns_width = max(self.btn_cancel.width(), self.btn_restart.width())
self.btn_cancel.setFixedWidth(btns_width)
self.btn_restart.setFixedWidth(btns_width)
def on_cancel_pressed(self):
self.done(0)
def on_restart_pressed(self):
self.done(1)
class SpacerWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(SpacerWidget, self).__init__(parent)

View file

@ -4,7 +4,7 @@ from .categories import (
SystemWidget,
ProjectWidget
)
from .widgets import ShadowWidget
from .widgets import ShadowWidget, RestartDialog
from . import style
from openpype.tools.settings import (
@ -14,6 +14,8 @@ from openpype.tools.settings import (
class MainWidget(QtWidgets.QWidget):
trigger_restart = QtCore.Signal()
widget_width = 1000
widget_height = 600
@ -60,6 +62,9 @@ class MainWidget(QtWidgets.QWidget):
for tab_widget in tab_widgets:
tab_widget.saved.connect(self._on_tab_save)
tab_widget.state_changed.connect(self._on_state_change)
tab_widget.restart_required_trigger.connect(
self._on_restart_required
)
self.tab_widgets = tab_widgets
@ -132,3 +137,15 @@ class MainWidget(QtWidgets.QWidget):
for tab_widget in self.tab_widgets:
tab_widget.reset()
def _on_restart_required(self):
# Don't show dialog if there are not registered slots for
# `trigger_restart` signal.
# - For example when settings are runnin as standalone tool
if self.receivers(self.trigger_restart) < 1:
return
dialog = RestartDialog(self)
result = dialog.exec_()
if result == 1:
self.trigger_restart.emit()

View file

@ -1,10 +1,13 @@
import os
import sys
import atexit
import subprocess
import platform
from avalon import style
from Qt import QtCore, QtGui, QtWidgets
from openpype.api import Logger, resources
from openpype.lib import get_pype_execute_args
from openpype.modules import TrayModulesManager, ITrayService
from openpype.settings.lib import get_system_settings
import openpype.version
@ -92,6 +95,34 @@ class TrayManager:
self.tray_widget.menu.addAction(version_action)
self.tray_widget.menu.addSeparator()
def restart(self):
"""Restart Tray tool.
First creates new process with same argument and close current tray.
"""
args = get_pype_execute_args()
# Create a copy of sys.argv
additional_args = list(sys.argv)
# Check last argument from `get_pype_execute_args`
# - when running from code it is the same as first from sys.argv
if args[-1] == additional_args[0]:
additional_args.pop(0)
args.extend(additional_args)
kwargs = {}
if platform.system().lower() == "windows":
flags = (
subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.DETACHED_PROCESS
)
kwargs["creationflags"] = flags
subprocess.Popen(args, **kwargs)
self.exit()
def exit(self):
self.tray_widget.exit()
def on_exit(self):
self.modules_manager.on_exit()
@ -116,6 +147,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
super(SystemTrayIcon, self).__init__(icon, parent)
self._exited = False
# Store parent - QtWidgets.QMainWindow()
self.parent = parent
@ -134,6 +167,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
# Add menu to Context of SystemTrayIcon
self.setContextMenu(self.menu)
atexit.register(self.exit)
def on_systray_activated(self, reason):
# show contextMenu if left click
if reason == QtWidgets.QSystemTrayIcon.Trigger:
@ -145,6 +180,10 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
- Icon won't stay in tray after exit.
"""
if self._exited:
return
self._exited = True
self.hide()
self.tray_man.on_exit()
QtCore.QCoreApplication.exit()