mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #1550 from pypeclub/feature/910-ask-user-to-restart-after-changing-global-environments-in-settings
This commit is contained in:
commit
194c81b0dc
12 changed files with 217 additions and 8 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"key": "ftrack",
|
||||
"label": "Ftrack",
|
||||
"collapsible": true,
|
||||
"require_restart": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue