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) output.append(module)
return output return output
def restart_tray(self):
if self.tray_manager:
self.tray_manager.restart()
def tray_init(self): def tray_init(self):
report = {} report = {}
time_start = time.time() time_start = time.time()

View file

@ -67,6 +67,10 @@ class SettingsAction(PypeModule, ITrayAction):
return return
from openpype.tools.settings import MainWidget from openpype.tools.settings import MainWidget
self.settings_window = MainWidget(self.user_role) 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): def show_settings_window(self):
"""Show settings tool window. """Show settings tool window.

View file

@ -111,6 +111,8 @@ class BaseItemEntity(BaseEntity):
self.file_item = None self.file_item = None
# Reference to `RootEntity` # Reference to `RootEntity`
self.root_item = None 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 # Entity is in hierarchy of dynamically created entity
self.is_in_dynamic_item = False self.is_in_dynamic_item = False
@ -171,6 +173,14 @@ class BaseItemEntity(BaseEntity):
roles = [roles] roles = [roles]
self.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 @property
def has_studio_override(self): def has_studio_override(self):
"""Says if entity or it's children has studio overrides.""" """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." 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 @abstractmethod
def set_override_state(self, state): def set_override_state(self, state):
"""Set override state and trigger it on children. """Set override state and trigger it on children.
@ -788,6 +806,15 @@ class ItemEntity(BaseItemEntity):
# Root item reference # Root item reference
self.root_item = self.parent.root_item 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 # File item reference
if self.parent.is_file: if self.parent.is_file:
self.file_item = self.parent self.file_item = self.parent

View file

@ -68,8 +68,18 @@ class EndpointEntity(ItemEntity):
def on_change(self): def on_change(self):
for callback in self.on_change_callbacks: for callback in self.on_change_callbacks:
callback() 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) self.parent.on_child_change(self)
@property
def require_restart(self):
return self.has_unsaved_changes
def update_default_value(self, value): def update_default_value(self, value):
value = self._check_update_value(value, "default") value = self._check_update_value(value, "default")
self._default_value = value self._default_value = value
@ -115,6 +125,10 @@ class InputEntity(EndpointEntity):
"""Entity's value without metadata.""" """Entity's value without metadata."""
return self._current_value return self._current_value
@property
def require_restart(self):
return self._value_is_modified
def _settings_value(self): def _settings_value(self):
return copy.deepcopy(self.value) return copy.deepcopy(self.value)

View file

@ -55,6 +55,8 @@ class RootEntity(BaseItemEntity):
def __init__(self, schema_data, reset): def __init__(self, schema_data, reset):
super(RootEntity, self).__init__(schema_data) super(RootEntity, self).__init__(schema_data)
self._require_restart_callbacks = []
self._item_ids_require_restart = set()
self._item_initalization() self._item_initalization()
if reset: if reset:
self.reset() self.reset()
@ -64,6 +66,31 @@ class RootEntity(BaseItemEntity):
"""Current OverrideState.""" """Current OverrideState."""
return self._override_state 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 @abstractmethod
def reset(self): def reset(self):
"""Reset values and entities to initial state. """Reset values and entities to initial state.

View file

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

View file

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

View file

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

View file

@ -73,6 +73,7 @@ class IgnoreInputChangesObj:
class SettingsCategoryWidget(QtWidgets.QWidget): class SettingsCategoryWidget(QtWidgets.QWidget):
state_changed = QtCore.Signal() state_changed = QtCore.Signal()
saved = QtCore.Signal(QtWidgets.QWidget) saved = QtCore.Signal(QtWidgets.QWidget)
restart_required_trigger = QtCore.Signal()
def __init__(self, user_role, parent=None): def __init__(self, user_role, parent=None):
super(SettingsCategoryWidget, self).__init__(parent) super(SettingsCategoryWidget, self).__init__(parent)
@ -185,9 +186,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
if self.user_role == "developer": if self.user_role == "developer":
self._add_developer_ui(footer_layout) self._add_developer_ui(footer_layout)
save_btn = QtWidgets.QPushButton("Save") save_btn = QtWidgets.QPushButton("Save", footer_widget)
spacer_widget = QtWidgets.QWidget() require_restart_label = QtWidgets.QLabel(footer_widget)
footer_layout.addWidget(spacer_widget, 1) require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
footer_layout.addWidget(require_restart_label, 1)
footer_layout.addWidget(save_btn, 0) footer_layout.addWidget(save_btn, 0)
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
@ -205,6 +207,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
save_btn.clicked.connect(self._save) save_btn.clicked.connect(self._save)
self.save_btn = save_btn self.save_btn = save_btn
self.require_restart_label = require_restart_label
self.scroll_widget = scroll_widget self.scroll_widget = scroll_widget
self.content_layout = content_layout self.content_layout = content_layout
self.content_widget = content_widget self.content_widget = content_widget
@ -323,6 +326,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
def _on_reset_start(self): def _on_reset_start(self):
return 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): def reset(self):
self.set_state(CategoryState.Working) self.set_state(CategoryState.Working)
@ -339,6 +351,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
dialog = None dialog = None
try: try:
self._create_root_entity() self._create_root_entity()
self.entity.add_require_restart_change_callback(
self._on_require_restart_change
)
self.add_children_gui() self.add_children_gui()
@ -433,6 +448,15 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
return return
def _save(self): 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) self.set_state(CategoryState.Working)
if self.items_are_valid(): if self.items_are_valid():
@ -442,6 +466,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self.saved.emit(self) self.saved.emit(self)
if require_restart:
self.restart_required_trigger.emit()
self.require_restart_label.setText("")
def _on_refresh(self): def _on_refresh(self):
self.reset() self.reset()

View file

@ -275,8 +275,6 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
layout.addWidget(message_label) layout.addWidget(message_label)
layout.addWidget(btns_widget) layout.addWidget(btns_widget)
self.state = None
def on_cancel_pressed(self): def on_cancel_pressed(self):
self.done(0) self.done(0)
@ -287,6 +285,48 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
self.done(2) 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): class SpacerWidget(QtWidgets.QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(SpacerWidget, self).__init__(parent) super(SpacerWidget, self).__init__(parent)

View file

@ -4,7 +4,7 @@ from .categories import (
SystemWidget, SystemWidget,
ProjectWidget ProjectWidget
) )
from .widgets import ShadowWidget from .widgets import ShadowWidget, RestartDialog
from . import style from . import style
from openpype.tools.settings import ( from openpype.tools.settings import (
@ -14,6 +14,8 @@ from openpype.tools.settings import (
class MainWidget(QtWidgets.QWidget): class MainWidget(QtWidgets.QWidget):
trigger_restart = QtCore.Signal()
widget_width = 1000 widget_width = 1000
widget_height = 600 widget_height = 600
@ -60,6 +62,9 @@ class MainWidget(QtWidgets.QWidget):
for tab_widget in tab_widgets: for tab_widget in tab_widgets:
tab_widget.saved.connect(self._on_tab_save) tab_widget.saved.connect(self._on_tab_save)
tab_widget.state_changed.connect(self._on_state_change) tab_widget.state_changed.connect(self._on_state_change)
tab_widget.restart_required_trigger.connect(
self._on_restart_required
)
self.tab_widgets = tab_widgets self.tab_widgets = tab_widgets
@ -132,3 +137,15 @@ class MainWidget(QtWidgets.QWidget):
for tab_widget in self.tab_widgets: for tab_widget in self.tab_widgets:
tab_widget.reset() 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 os
import sys import sys
import atexit
import subprocess
import platform import platform
from avalon import style from avalon import style
from Qt import QtCore, QtGui, QtWidgets from Qt import QtCore, QtGui, QtWidgets
from openpype.api import Logger, resources from openpype.api import Logger, resources
from openpype.lib import get_pype_execute_args
from openpype.modules import TrayModulesManager, ITrayService from openpype.modules import TrayModulesManager, ITrayService
from openpype.settings.lib import get_system_settings from openpype.settings.lib import get_system_settings
import openpype.version import openpype.version
@ -92,6 +95,34 @@ class TrayManager:
self.tray_widget.menu.addAction(version_action) self.tray_widget.menu.addAction(version_action)
self.tray_widget.menu.addSeparator() 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): def on_exit(self):
self.modules_manager.on_exit() self.modules_manager.on_exit()
@ -116,6 +147,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
super(SystemTrayIcon, self).__init__(icon, parent) super(SystemTrayIcon, self).__init__(icon, parent)
self._exited = False
# Store parent - QtWidgets.QMainWindow() # Store parent - QtWidgets.QMainWindow()
self.parent = parent self.parent = parent
@ -134,6 +167,8 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
# Add menu to Context of SystemTrayIcon # Add menu to Context of SystemTrayIcon
self.setContextMenu(self.menu) self.setContextMenu(self.menu)
atexit.register(self.exit)
def on_systray_activated(self, reason): def on_systray_activated(self, reason):
# show contextMenu if left click # show contextMenu if left click
if reason == QtWidgets.QSystemTrayIcon.Trigger: if reason == QtWidgets.QSystemTrayIcon.Trigger:
@ -145,6 +180,10 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
- Icon won't stay in tray after exit. - Icon won't stay in tray after exit.
""" """
if self._exited:
return
self._exited = True
self.hide() self.hide()
self.tray_man.on_exit() self.tray_man.on_exit()
QtCore.QCoreApplication.exit() QtCore.QCoreApplication.exit()