Merge pull request #1049 from pypeclub/feature/local_settings

Local settings
This commit is contained in:
Milan Kolar 2021-02-26 11:10:27 +01:00 committed by GitHub
commit d8d16ca80e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1833 additions and 13 deletions

View file

@ -22,7 +22,9 @@ from .lib import (
get_app_environments_for_context,
source_hash,
get_latest_version,
get_global_environments
get_global_environments,
get_local_site_id,
change_pype_mongo_url
)
from .lib.mongo import (
@ -109,5 +111,8 @@ __all__ = [
"run_subprocess",
"get_latest_version",
"get_global_environments"
"get_global_environments",
"get_local_site_id",
"change_pype_mongo_url"
]

View file

@ -23,6 +23,7 @@ from .mongo import (
decompose_url,
compose_url,
get_default_components,
validate_mongo_connection,
PypeMongoConnection
)
from .anatomy import (
@ -91,10 +92,12 @@ from .plugin_tools import (
should_decompress
)
from .user_settings import (
from .local_settings import (
IniSettingRegistry,
JSONSettingRegistry,
PypeSettingsRegistry
PypeSettingsRegistry,
get_local_site_id,
change_pype_mongo_url
)
from .path_tools import (
@ -191,11 +194,15 @@ __all__ = [
"decompose_url",
"compose_url",
"get_default_components",
"validate_mongo_connection",
"PypeMongoConnection",
"IniSettingRegistry",
"JSONSettingRegistry",
"PypeSettingsRegistry",
"get_local_site_id",
"change_pype_mongo_url",
"timeit",
"is_overlapping_otio_ranges",

View file

@ -28,6 +28,8 @@ import platform
import appdirs
import six
from .import validate_mongo_connection
@six.add_metaclass(ABCMeta)
class ASettingRegistry():
@ -118,7 +120,7 @@ class ASettingRegistry():
"""Delete item from settings.
Note:
see :meth:`pype.lib.user_settings.ARegistrySettings.delete_item`
see :meth:`pype.lib.local_settings.ARegistrySettings.delete_item`
"""
pass
@ -464,3 +466,43 @@ class PypeSettingsRegistry(JSONSettingRegistry):
self.product = "pype"
path = appdirs.user_data_dir(self.product, self.vendor)
super(PypeSettingsRegistry, self).__init__("pype_settings", path)
def _create_local_site_id(registry=None):
"""Create a local site identifier."""
from uuid import uuid4
if registry is None:
registry = PypeSettingsRegistry()
new_id = str(uuid4())
print("Created local site id \"{}\"".format(new_id))
registry.set_item("localId", new_id)
return new_id
def get_local_site_id():
"""Get local site identifier.
Identifier is created if does not exists yet.
"""
registry = PypeSettingsRegistry()
try:
return registry.get_item("localId")
except ValueError:
return _create_local_site_id()
def change_pype_mongo_url(new_mongo_url):
"""Change mongo url in pype registry.
Change of Pype mongo URL require restart of running pype processes or
processes using pype.
"""
validate_mongo_connection(new_mongo_url)
registry = PypeSettingsRegistry()
registry.set_secure_item("pypeMongo", new_mongo_url)

View file

@ -93,6 +93,42 @@ def extract_port_from_url(url):
return parsed_url.port
def validate_mongo_connection(mongo_uri):
"""Check if provided mongodb URL is valid.
Args:
mongo_uri (str): URL to validate.
Raises:
ValueError: When port in mongo uri is not valid.
pymongo.errors.InvalidURI: If passed mongo is invalid.
pymongo.errors.ServerSelectionTimeoutError: If connection timeout
passed so probably couldn't connect to mongo server.
"""
parsed = urlparse(mongo_uri)
# Force validation of scheme
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
raise pymongo.errors.InvalidURI((
"Invalid URI scheme:"
" URI must begin with 'mongodb://' or 'mongodb+srv://'"
))
# we have mongo connection string. Let's try if we can connect.
components = decompose_url(mongo_uri)
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 1000
}
port = components.get("port")
if port is not None:
mongo_args["port"] = int(port)
# Create connection
client = pymongo.MongoClient(**mongo_args)
client.server_info()
client.close()
class PypeMongoConnection:
"""Singleton MongoDB connection.

View file

@ -9,7 +9,10 @@ from .base import (
ModulesManager,
TrayModulesManager
)
from .settings_action import SettingsAction
from .settings_action import (
SettingsAction,
LocalSettingsAction
)
from .rest_api import (
RestApiModule,
IRestApi
@ -52,6 +55,7 @@ __all__ = (
"TrayModulesManager",
"SettingsAction",
"LocalSettingsAction",
"UserModule",
"IUserModule",

View file

@ -627,6 +627,7 @@ class TrayModulesManager(ModulesManager):
"clockify",
"standalonepublish_tool",
"log_viewer",
"local_settings",
"settings"
)

View file

@ -58,3 +58,58 @@ class SettingsAction(PypeModule, ITrayAction):
# Reset content if was not visible
if not was_visible:
self.settings_window.reset()
class LocalSettingsAction(PypeModule, ITrayAction):
"""Action to show Setttings tool."""
name = "local_settings"
label = "Local Settings"
def initialize(self, _modules_settings):
# This action is always enabled
self.enabled = True
# Tray attributes
self.settings_window = None
def connect_with_modules(self, *_a, **_kw):
return
def tray_init(self):
"""Initialization in tray implementation of ITrayAction."""
self.create_settings_window()
def on_action_trigger(self):
"""Implementation for action trigger of ITrayAction."""
self.show_settings_window()
def create_settings_window(self):
"""Initializa Settings Qt window."""
if self.settings_window:
return
from pype.tools.settings import LocalSettingsWindow
self.settings_window = LocalSettingsWindow()
def show_settings_window(self):
"""Show settings tool window.
Raises:
AssertionError: Window must be already created. Call
`create_settings_window` before callint this method.
"""
if not self.settings_window:
raise AssertionError("Window is not initialized.")
# Store if was visible
was_visible = self.settings_window.isVisible()
# Show settings gui
self.settings_window.show()
# Pull window to the front.
self.settings_window.raise_()
self.settings_window.activateWindow()
# Reset content if was not visible
if not was_visible:
self.settings_window.reset()

View file

@ -15,7 +15,9 @@ METADATA_KEYS = (
SYSTEM_SETTINGS_KEY = "system_settings"
PROJECT_SETTINGS_KEY = "project_settings"
PROJECT_ANATOMY_KEY = "project_anatomy"
LOCAL_SETTING_KEY = "local_settings"
DEFAULT_PROJECT_KEY = "__default_project__"
__all__ = (
"M_OVERRIDEN_KEY",
@ -26,5 +28,6 @@ __all__ = (
"SYSTEM_SETTINGS_KEY",
"PROJECT_SETTINGS_KEY",
"PROJECT_ANATOMY_KEY"
"PROJECT_ANATOMY_KEY",
"LOCAL_SETTING_KEY"
)

View file

@ -10,7 +10,8 @@ import pype
from .constants import (
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY
PROJECT_ANATOMY_KEY,
LOCAL_SETTING_KEY
)
from .lib import load_json_file
@ -103,6 +104,28 @@ class SettingsHandler:
pass
@six.add_metaclass(ABCMeta)
class LocalSettingsHandler:
"""Handler that should handle about storing and loading of local settings.
Local settings are "workstation" specific modifications that modify how
system and project settings look on the workstation and only there.
"""
@abstractmethod
def save_local_settings(self, data):
"""Save local data of local settings.
Args:
data(dict): Data of local data with override metadata.
"""
pass
@abstractmethod
def get_local_settings(self):
"""Studio overrides of system settings."""
pass
class SettingsFileHandler(SettingsHandler):
def __init__(self):
self.log = logging.getLogger("SettingsFileHandler")
@ -495,3 +518,76 @@ class MongoSettingsHandler(SettingsHandler):
if not project_name:
return {}
return self._get_project_anatomy_overrides(project_name)
class MongoLocalSettingsHandler(LocalSettingsHandler):
"""Settings handler that use mongo for store and load local settings.
Data have 2 query criteria. First is key "type" stored in constant
`LOCAL_SETTING_KEY`. Second is key "site_id" which value can be obstained
with `get_local_site_id` function.
"""
def __init__(self, local_site_id=None):
# Get mongo connection
from pype.lib import (
PypeMongoConnection,
get_local_site_id
)
if local_site_id is None:
local_site_id = get_local_site_id()
settings_collection = PypeMongoConnection.get_mongo_client()
# TODO prepare version of pype
# - pype version should define how are settings saved and loaded
# TODO modify to not use hardcoded keys
database_name = "pype"
collection_name = "settings"
self.settings_collection = settings_collection
self.database_name = database_name
self.collection_name = collection_name
self.collection = settings_collection[database_name][collection_name]
self.local_site_id = local_site_id
self.local_settings_cache = CacheValues()
def save_local_settings(self, data):
"""Save local settings.
Args:
data(dict): Data of studio overrides with override metadata.
"""
data = data or {}
self.local_settings_cache.update_data(data)
self.collection.replace_one(
{
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id
},
{
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id,
"value": self.local_settings_cache.to_json_string()
},
upsert=True
)
def get_local_settings(self):
"""Local settings for local site id."""
if self.local_settings_cache.is_outdated:
document = self.collection.find_one({
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id
})
self.local_settings_cache.update_from_document(document)
return self.local_settings_cache.data_copy()

View file

@ -2,6 +2,7 @@ import os
import json
import functools
import logging
import platform
import copy
from .constants import (
M_OVERRIDEN_KEY,
@ -11,7 +12,8 @@ from .constants import (
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY
PROJECT_ANATOMY_KEY,
DEFAULT_PROJECT_KEY
)
log = logging.getLogger(__name__)
@ -32,6 +34,9 @@ _DEFAULT_SETTINGS = None
# Handler of studio overrides
_SETTINGS_HANDLER = None
# Handler of local settings
_LOCAL_SETTINGS_HANDLER = None
def require_handler(func):
@functools.wraps(func)
@ -43,6 +48,16 @@ def require_handler(func):
return wrapper
def require_local_handler(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
global _LOCAL_SETTINGS_HANDLER
if _LOCAL_SETTINGS_HANDLER is None:
_LOCAL_SETTINGS_HANDLER = create_local_settings_handler()
return func(*args, **kwargs)
return wrapper
def create_settings_handler():
from .handlers import MongoSettingsHandler
# Handler can't be created in global space on initialization but only when
@ -50,6 +65,11 @@ def create_settings_handler():
return MongoSettingsHandler()
def create_local_settings_handler():
from .handlers import MongoLocalSettingsHandler
return MongoLocalSettingsHandler()
@require_handler
def save_studio_settings(data):
return _SETTINGS_HANDLER.save_studio_settings(data)
@ -90,6 +110,16 @@ def get_project_anatomy_overrides(project_name):
return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
@require_local_handler
def save_local_settings(data):
return _LOCAL_SETTINGS_HANDLER.save_local_settings(data)
@require_local_handler
def get_local_settings():
return _LOCAL_SETTINGS_HANDLER.get_local_settings()
class DuplicatedEnvGroups(Exception):
def __init__(self, duplicated):
self.origin_duplicated = duplicated
@ -309,6 +339,109 @@ def apply_overrides(source_data, override_data):
return merge_overrides(_source_data, override_data)
def apply_local_settings_on_system_settings(system_settings, local_settings):
"""Apply local settings on studio system settings.
ATM local settings can modify only application executables. Executable
values are not overriden but prepended.
"""
if not local_settings or "applications" not in local_settings:
return
current_platform = platform.system().lower()
for app_group_name, value in local_settings["applications"].items():
if not value or app_group_name not in system_settings["applications"]:
continue
variants = system_settings["applications"][app_group_name]["variants"]
for app_name, app_value in value.items():
if not app_value or app_name not in variants:
continue
executable = app_value.get("executable")
if not executable:
continue
platform_executables = variants[app_name]["executables"].get(
current_platform
)
# TODO This is temporary fix until launch arguments will be stored
# per platform and not per executable.
# - local settings store only executable
new_executables = [[executable, ""]]
new_executables.extend(platform_executables)
variants[app_name]["executables"] = new_executables
def apply_local_settings_on_anatomy_settings(
anatomy_settings, local_settings, project_name
):
"""Apply local settings on anatomy settings.
ATM local settings can modify project roots. Project name is required as
local settings have data stored data by project's name.
Local settings override root values in this order:
1.) Check if local settings contain overrides for default project and
apply it's values on roots if there are any.
2.) If passed `project_name` is not None then check project specific
overrides in local settings for the project and apply it's value on
roots if there are any.
NOTE: Root values of default project from local settings are always applied
if are set.
Args:
anatomy_settings (dict): Data for anatomy settings.
local_settings (dict): Data of local settings.
project_name (str): Name of project for which anatomy data are.
"""
if not local_settings:
return
local_project_settings = local_settings.get("projects")
if not local_project_settings:
return
project_locals = local_project_settings.get(project_name) or {}
default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {}
active_site = project_locals.get("active_site")
if not active_site:
active_site = default_locals.get("active_site")
if not active_site:
project_settings = get_project_settings(project_name)
active_site = (
project_settings
["global"]
["sync_server"]
["config"]
["active_site"]
)
# QUESTION should raise an exception?
if not active_site:
return
roots_locals = default_locals.get("roots", {}).get(active_site, {})
if project_name != DEFAULT_PROJECT_KEY:
roots_locals.update(
project_locals.get("roots", {}).get(active_site, {})
)
if not roots_locals:
return
current_platform = platform.system().lower()
root_data = anatomy_settings["roots"]
for root_name, path in roots_locals.items():
if root_name not in root_data:
continue
anatomy_settings["roots"][root_name][current_platform] = (
path
)
def get_system_settings(clear_metadata=True):
"""System settings with applied studio overrides."""
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
@ -316,6 +449,10 @@ def get_system_settings(clear_metadata=True):
result = apply_overrides(default_values, studio_values)
if clear_metadata:
clear_metadata_from_settings(result)
# TODO local settings may be required to apply for environments
local_settings = get_local_settings()
apply_local_settings_on_system_settings(result, local_settings)
return result
@ -343,6 +480,8 @@ def get_default_anatomy_settings(clear_metadata=True):
result[key] = value
if clear_metadata:
clear_metadata_from_settings(result)
local_settings = get_local_settings()
apply_local_settings_on_anatomy_settings(result, local_settings, None)
return result
@ -368,6 +507,10 @@ def get_anatomy_settings(project_name, clear_metadata=True):
result[key] = value
if clear_metadata:
clear_metadata_from_settings(result)
local_settings = get_local_settings()
apply_local_settings_on_anatomy_settings(
result, local_settings, project_name
)
return result

View file

@ -0,0 +1,79 @@
# Structure of local settings
- local settings do not have any validation schemas right now this should help to see what is stored to local settings and how it works
- they are stored by identifier site_id which should be unified identifier of workstation
- all keys may and may not available on load
- contain main categories: `general`, `applications`, `projects`
## Categories
### General
- ATM contain only label of site
```json
{
"general": {
"site_label": "MySite"
}
}
```
### Applications
- modifications of application executables
- output should match application groups and variants
```json
{
"applications": {
"<app group>": {
"<app name>": {
"executable": "/my/path/to/nuke_12_2"
}
}
}
}
```
### Projects
- project specific modifications
- default project is stored under constant key defined in `pype.settings.contants`
```json
{
"projects": {
"<project name>": {
"active_site": "<name of active site>",
"remote_site": "<name of remote site>",
"roots": {
"<site name>": {
"<root name>": "<root dir path>"
}
}
}
}
}
```
## Final document
```json
{
"_id": "<ObjectId(...)>",
"site_id": "<site id>",
"general": {
"site_label": "MySite"
},
"applications": {
"<app group>": {
"<app name>": {
"executable": "<path to app executable>"
}
}
},
"projects": {
"<project name>": {
"active_site": "<name of active site>",
"remote_site": "<name of remote site>",
"roots": {
"<site name>": {
"<root name>": "<root dir path>"
}
}
}
}
}
```

View file

@ -1,6 +1,6 @@
import sys
from Qt import QtWidgets, QtGui
from .local_settings import LocalSettingsWindow
from .settings import (
style,
MainWidget,
@ -33,5 +33,6 @@ __all__ = (
"style",
"MainWidget",
"ProjectListWidget",
"LocalSettingsWindow",
"main"
)

View file

@ -0,0 +1,6 @@
from .window import LocalSettingsWindow
__all__ = (
"LocalSettingsWindow",
)

View file

@ -0,0 +1,205 @@
import platform
from Qt import QtWidgets
from .widgets import (
Separator,
ExpandingWidget
)
from .constants import CHILD_OFFSET
class AppVariantWidget(QtWidgets.QWidget):
exec_placeholder = "< Specific path for this machine >"
def __init__(self, group_label, variant_entity, parent):
super(AppVariantWidget, self).__init__(parent)
self.executable_input_widget = None
label = " ".join([group_label, variant_entity.label])
expading_widget = ExpandingWidget(label, self)
content_widget = QtWidgets.QWidget(expading_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
expading_widget.set_content_widget(content_widget)
# Add expanding widget to main layout
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(expading_widget)
# TODO For celaction - not sure what is "Celaction publish" for
if not variant_entity["executables"].multiplatform:
warn_label = QtWidgets.QLabel(
"Application without multiplatform paths"
)
content_layout.addWidget(warn_label)
return
executable_input_widget = QtWidgets.QLineEdit(content_widget)
executable_input_widget.setPlaceholderText(self.exec_placeholder)
content_layout.addWidget(executable_input_widget)
self.executable_input_widget = executable_input_widget
studio_executables = (
variant_entity["executables"][platform.system().lower()]
)
if len(studio_executables) < 1:
return
content_layout.addWidget(Separator(parent=self))
content_layout.addWidget(
QtWidgets.QLabel("Studio paths:", self)
)
for item in studio_executables:
path_widget = QtWidgets.QLineEdit(content_widget)
path_widget.setText(item.value[0])
path_widget.setEnabled(False)
content_layout.addWidget(path_widget)
def update_local_settings(self, value):
if not self.executable_input_widget:
return
if not value:
value = {}
elif not isinstance(value, dict):
print("Got invalid value type {}. Expected {}".format(
type(value), dict
))
value = {}
executable_path = value.get("executable")
if not executable_path:
executable_path = ""
elif isinstance(executable_path, list):
print("Got list in executable path so using first item as value")
executable_path = executable_path[0]
if not isinstance(executable_path, str):
executable_path = ""
print((
"Got invalid value type of app executable {}. Expected {}"
).format(type(value), str))
self.executable_input_widget.setText(executable_path)
def settings_value(self):
if not self.executable_input_widget:
return None
value = self.executable_input_widget.text()
if not value:
return None
return {"executable": value}
class AppGroupWidget(QtWidgets.QWidget):
def __init__(self, group_entity, parent):
super(AppGroupWidget, self).__init__(parent)
valid_variants = {}
for key, entity in group_entity["variants"].items():
if entity["enabled"].value:
valid_variants[key] = entity
group_label = group_entity.label
expading_widget = ExpandingWidget(group_label, self)
content_widget = QtWidgets.QWidget(expading_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
widgets_by_variant_name = {}
for variant_name, variant_entity in valid_variants.items():
variant_widget = AppVariantWidget(
group_label, variant_entity, content_widget
)
widgets_by_variant_name[variant_name] = variant_widget
content_layout.addWidget(variant_widget)
expading_widget.set_content_widget(content_widget)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(expading_widget)
self.widgets_by_variant_name = widgets_by_variant_name
def update_local_settings(self, value):
if not value:
value = {}
for variant_name, widget in self.widgets_by_variant_name.items():
widget.update_local_settings(value.get(variant_name))
def settings_value(self):
output = {}
for variant_name, widget in self.widgets_by_variant_name.items():
value = widget.settings_value()
if value:
output[variant_name] = value
if not output:
return None
return output
class LocalApplicationsWidgets(QtWidgets.QWidget):
def __init__(self, system_settings_entity, parent):
super(LocalApplicationsWidgets, self).__init__(parent)
self.widgets_by_group_name = {}
self.system_settings_entity = system_settings_entity
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.content_layout = layout
def _reset_app_widgets(self):
while self.content_layout.count() > 0:
item = self.content_layout.itemAt(0)
item.widget().hide()
self.content_layout.removeItem(item)
self.widgets_by_group_name.clear()
for key, entity in self.system_settings_entity["applications"].items():
# Filter not enabled app groups
if not entity["enabled"].value:
continue
# Check if has enabled any variant
enabled_variant = False
for variant_entity in entity["variants"].values():
if variant_entity["enabled"].value:
enabled_variant = True
break
if not enabled_variant:
continue
# Create App group specific widget and store it by the key
group_widget = AppGroupWidget(entity, self)
self.widgets_by_group_name[key] = group_widget
self.content_layout.addWidget(group_widget)
def update_local_settings(self, value):
if not value:
value = {}
self._reset_app_widgets()
for group_name, widget in self.widgets_by_group_name.items():
widget.update_local_settings(value.get(group_name))
def settings_value(self):
output = {}
for group_name, widget in self.widgets_by_group_name.items():
value = widget.settings_value()
if value:
output[group_name] = value
if not output:
return None
return output

View file

@ -0,0 +1,32 @@
# Action labels
LABEL_REMOVE_DEFAULT = "Remove from default"
LABEL_ADD_DEFAULT = "Add to default"
LABEL_REMOVE_PROJECT = "Remove from project"
LABEL_ADD_PROJECT = "Add to project"
LABEL_DISCARD_CHANGES = "Discard changes"
# Local setting contants
# TODO move to settings constants
LOCAL_GENERAL_KEY = "general"
LOCAL_PROJECTS_KEY = "projects"
LOCAL_APPS_KEY = "applications"
# Roots key constant
LOCAL_ROOTS_KEY = "roots"
# Child offset in expandable widget
CHILD_OFFSET = 15
__all__ = (
"LABEL_REMOVE_DEFAULT",
"LABEL_ADD_DEFAULT",
"LABEL_REMOVE_PROJECT",
"LABEL_ADD_PROJECT",
"LABEL_DISCARD_CHANGES",
"LOCAL_GENERAL_KEY",
"LOCAL_PROJECTS_KEY",
"LOCAL_APPS_KEY",
"LOCAL_ROOTS_KEY"
)

View file

@ -0,0 +1,32 @@
from Qt import QtWidgets
class LocalGeneralWidgets(QtWidgets.QWidget):
def __init__(self, parent):
super(LocalGeneralWidgets, self).__init__(parent)
local_site_name_input = QtWidgets.QLineEdit(self)
layout = QtWidgets.QFormLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addRow("Local site label", local_site_name_input)
self.local_site_name_input = local_site_name_input
def update_local_settings(self, value):
site_label = ""
if value:
site_label = value.get("site_label", site_label)
self.local_site_name_input.setText(site_label)
def settings_value(self):
# Add changed
# If these have changed then
output = {}
local_site_name = self.local_site_name_input.text()
if local_site_name:
output["site_label"] = local_site_name
# Do not return output yet since we don't have mechanism to save or
# load these data through api calls
return output

View file

@ -0,0 +1,80 @@
import os
import sys
import traceback
from Qt import QtWidgets
from pymongo.errors import ServerSelectionTimeoutError
from pype.api import change_pype_mongo_url
class PypeMongoWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(PypeMongoWidget, self).__init__(parent)
# Warning label
warning_label = QtWidgets.QLabel((
"WARNING: Requires restart. Change of Pype Mongo requires to"
" restart of all running Pype processes and process using Pype"
" (Including this)."
"\n- all changes in different categories won't be saved."
), self)
warning_label.setStyleSheet("font-weight: bold;")
# Label
mongo_url_label = QtWidgets.QLabel("Pype Mongo URL", self)
# Input
mongo_url_input = QtWidgets.QLineEdit(self)
mongo_url_input.setPlaceholderText("< Pype Mongo URL >")
mongo_url_input.setText(os.environ["PYPE_MONGO"])
# Confirm button
mongo_url_change_btn = QtWidgets.QPushButton("Confirm Change", self)
layout = QtWidgets.QGridLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(warning_label, 0, 0, 1, 3)
layout.addWidget(mongo_url_label, 1, 0)
layout.addWidget(mongo_url_input, 1, 1)
layout.addWidget(mongo_url_change_btn, 1, 2)
mongo_url_change_btn.clicked.connect(self._on_confirm_click)
self.mongo_url_input = mongo_url_input
def _on_confirm_click(self):
value = self.mongo_url_input.text()
dialog = QtWidgets.QMessageBox(self)
title = "Pype mongo changed"
message = (
"Pype mongo url was successfully changed. Restart Pype please."
)
details = None
try:
change_pype_mongo_url(value)
except Exception as exc:
if isinstance(exc, ServerSelectionTimeoutError):
error_message = (
"Connection timeout passed."
" Probably can't connect to the Mongo server."
)
else:
error_message = str(exc)
title = "Pype mongo change failed"
# TODO catch exception message more gracefully
message = (
"Pype mongo change was not successful."
" Full traceback can be found in details section.\n\n"
"Error message:\n{}"
).format(error_message)
details = "\n".join(traceback.format_exception(*sys.exc_info()))
dialog.setWindowTitle(title)
dialog.setText(message)
if details:
dialog.setDetailedText(details)
dialog.exec_()

View file

@ -0,0 +1,731 @@
import platform
import copy
from Qt import QtWidgets, QtCore, QtGui
from pype.tools.settings.settings import ProjectListWidget
from pype.settings.constants import (
PROJECT_ANATOMY_KEY,
DEFAULT_PROJECT_KEY
)
from .widgets import (
SpacerWidget,
ProxyLabelWidget
)
from .constants import (
LABEL_REMOVE_DEFAULT,
LABEL_ADD_DEFAULT,
LABEL_REMOVE_PROJECT,
LABEL_ADD_PROJECT,
LABEL_DISCARD_CHANGES,
LOCAL_ROOTS_KEY
)
NOT_SET = type("NOT_SET", (), {})()
def get_active_sites(project_settings):
global_entity = project_settings["project_settings"]["global"]
sites_entity = global_entity["sync_server"]["sites"]
return tuple(sites_entity.keys())
class _ProjectListWidget(ProjectListWidget):
def on_item_clicked(self, new_index):
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
if new_project_name is None:
return
if self.current_project == new_project_name:
return
self.select_project(new_project_name)
self.current_project = new_project_name
self.project_changed.emit()
class RootInputWidget(QtWidgets.QWidget):
def __init__(
self,
local_project_settings,
local_project_settings_orig,
platform_root_entity,
root_name,
project_name,
site_name,
parent
):
super(RootInputWidget, self).__init__(parent)
self.local_project_settings = local_project_settings
self.local_project_settings_orig = local_project_settings_orig
self.platform_root_entity = platform_root_entity
self.root_name = root_name
self.site_name = site_name
self.project_name = project_name
self.origin_value = self._get_site_value_for_project(
self.project_name, self.local_project_settings_orig
) or ""
is_default_project = bool(project_name == DEFAULT_PROJECT_KEY)
default_input_value = self._get_site_value_for_project(
DEFAULT_PROJECT_KEY
)
if is_default_project:
input_value = default_input_value
project_value = None
else:
input_value = self._get_site_value_for_project(self.project_name)
project_value = input_value
# Placeholder
placeholder = None
if not is_default_project:
placeholder = default_input_value
if not placeholder:
placeholder = platform_root_entity.value
key_label = ProxyLabelWidget(
root_name,
self._mouse_release_callback,
self
)
value_input = QtWidgets.QLineEdit(self)
value_input.setPlaceholderText("< {} >".format(placeholder))
# Root value
if input_value:
value_input.setText(input_value)
value_input.textChanged.connect(self._on_value_change)
root_layout = QtWidgets.QHBoxLayout(self)
root_layout.addWidget(key_label)
root_layout.addWidget(value_input)
self.value_input = value_input
self.label_widget = key_label
self.studio_value = platform_root_entity.value
self.default_value = default_input_value
self.project_value = project_value
self.placeholder_value = placeholder
self._update_style()
def is_modified(self):
return self.origin_value != self.value_input.text()
def _mouse_release_callback(self, event):
if event.button() != QtCore.Qt.RightButton:
return
self._show_actions()
event.accept()
def _get_style_state(self):
if self.project_name is None:
return ""
if self.is_modified():
return "modified"
current_value = self.value_input.text()
if self.project_name == DEFAULT_PROJECT_KEY:
if current_value:
return "studio"
else:
if current_value:
return "overriden"
studio_value = self._get_site_value_for_project(
DEFAULT_PROJECT_KEY
)
if studio_value:
return "studio"
return ""
def _update_style(self):
state = self._get_style_state()
self.value_input.setProperty("input-state", state)
self.value_input.style().polish(self.value_input)
self.label_widget.set_label_property("state", state)
def _remove_from_local(self):
self.value_input.setText("")
self._update_style()
def _add_to_local(self):
self.value_input.setText(self.placeholder_value)
self._update_style()
def discard_changes(self):
self.value_input.setText(self.origin_value)
self._update_style()
def _show_actions(self):
if self.project_name is None:
return
menu = QtWidgets.QMenu(self)
actions_mapping = {}
if self.project_name == DEFAULT_PROJECT_KEY:
remove_label = LABEL_REMOVE_DEFAULT
add_label = LABEL_ADD_DEFAULT
else:
remove_label = LABEL_REMOVE_PROJECT
add_label = LABEL_ADD_PROJECT
if self.value_input.text():
action = QtWidgets.QAction(remove_label)
callback = self._remove_from_local
else:
action = QtWidgets.QAction(add_label)
callback = self._add_to_local
actions_mapping[action] = callback
menu.addAction(action)
if self.is_modified():
discard_changes_action = QtWidgets.QAction(LABEL_DISCARD_CHANGES)
actions_mapping[discard_changes_action] = self.discard_changes
menu.addAction(discard_changes_action)
result = menu.exec_(QtGui.QCursor.pos())
if result:
to_run = actions_mapping[result]
if to_run:
to_run()
def _get_site_value_for_project(self, project_name, data=None):
if data is None:
data = self.local_project_settings
project_values = data.get(project_name)
site_value = {}
if project_values:
root_value = project_values.get(LOCAL_ROOTS_KEY)
if root_value:
site_value = root_value.get(self.site_name) or {}
return site_value.get(self.root_name)
def _on_value_change(self):
value = self.value_input.text()
data = self.local_project_settings
for key in (self.project_name, LOCAL_ROOTS_KEY, self.site_name):
if key not in data:
data[key] = {}
data = data[key]
data[self.root_name] = value
self._update_style()
class RootsWidget(QtWidgets.QWidget):
def __init__(self, project_settings, parent):
super(RootsWidget, self).__init__(parent)
self.project_settings = project_settings
self.site_widgets = []
self.local_project_settings = None
self.local_project_settings_orig = None
self._project_name = None
self.content_layout = QtWidgets.QVBoxLayout(self)
def _clear_widgets(self):
while self.content_layout.count():
item = self.content_layout.itemAt(0)
item.widget().hide()
self.content_layout.removeItem(item)
self.site_widgets = []
def refresh(self):
self._clear_widgets()
if self._project_name is None:
return
roots_entity = (
self.project_settings[PROJECT_ANATOMY_KEY][LOCAL_ROOTS_KEY]
)
# Site label
for site_name in get_active_sites(self.project_settings):
site_widget = QtWidgets.QWidget(self)
site_layout = QtWidgets.QVBoxLayout(site_widget)
site_label = QtWidgets.QLabel(site_name, site_widget)
site_layout.addWidget(site_label)
# Root inputs
for root_name, path_entity in roots_entity.items():
platform_entity = path_entity[platform.system().lower()]
root_widget = RootInputWidget(
self.local_project_settings,
self.local_project_settings_orig,
platform_entity,
root_name,
self._project_name,
site_name,
site_widget
)
site_layout.addWidget(root_widget)
self.site_widgets.append(site_widget)
self.content_layout.addWidget(site_widget)
# Add spacer so other widgets are squeezed to top
self.content_layout.addWidget(SpacerWidget(self), 1)
def update_local_settings(self, local_project_settings):
self.local_project_settings = local_project_settings
self.local_project_settings_orig = copy.deepcopy(
dict(local_project_settings)
)
def change_project(self, project_name):
self._project_name = project_name
self.refresh()
class _SiteCombobox(QtWidgets.QWidget):
input_label = None
def __init__(self, project_settings, parent):
super(_SiteCombobox, self).__init__(parent)
self.project_settings = project_settings
self.local_project_settings = None
self.local_project_settings_orig = None
self.project_name = None
self.default_override_value = None
self.project_override_value = None
label_widget = ProxyLabelWidget(
self.input_label,
self._mouse_release_callback,
self
)
combobox_input = QtWidgets.QComboBox(self)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.addWidget(label_widget)
main_layout.addWidget(combobox_input)
combobox_input.currentIndexChanged.connect(self._on_index_change)
self.label_widget = label_widget
self.combobox_input = combobox_input
def _set_current_text(self, text):
index = None
if text:
idx = self.combobox_input.findText(text)
if idx >= 0:
index = idx
if index is not None:
self.combobox_input.setCurrentIndex(index)
return True
return False
def is_modified(self, current_value=NOT_SET, orig_value=NOT_SET):
if current_value is NOT_SET:
current_value = self._get_local_settings_item(self.project_name)
if orig_value is NOT_SET:
orig_value = self._get_local_settings_item(
self.project_name, self.local_project_settings_orig
)
if current_value and orig_value:
modified = current_value != orig_value
elif not current_value and not orig_value:
modified = False
else:
modified = True
return modified
def _get_style_state(self):
if self.project_name is None:
return ""
current_value = self._get_local_settings_item(self.project_name)
orig_value = self._get_local_settings_item(
self.project_name, self.local_project_settings_orig
)
if self.is_modified(current_value, orig_value):
return "modified"
if self.project_name == DEFAULT_PROJECT_KEY:
if current_value:
return "studio"
else:
if current_value:
return "overriden"
studio_value = self._get_local_settings_item(DEFAULT_PROJECT_KEY)
if studio_value:
return "studio"
return ""
def _update_style(self):
state = self._get_style_state()
self.combobox_input.setProperty("input-state", state)
self.combobox_input.style().polish(self.combobox_input)
self.label_widget.set_label_property("state", state)
def _mouse_release_callback(self, event):
if event.button() != QtCore.Qt.RightButton:
return
self._show_actions()
def _remove_from_local(self):
settings_value = self._get_value_from_project_settings()
combobox_value = None
if self.project_name == DEFAULT_PROJECT_KEY:
combobox_value = self._get_local_settings_item(DEFAULT_PROJECT_KEY)
if combobox_value:
idx = self.combobox_input.findText(combobox_value)
if idx < 0:
combobox_value = None
if not combobox_value:
combobox_value = settings_value
if combobox_value:
_project_name = self.project_name
self.project_name = None
self._set_current_text(combobox_value)
self.project_name = _project_name
self._set_local_settings_value("")
self._update_style()
def _add_to_local(self):
self._set_local_settings_value(self.current_text())
self._update_style()
def discard_changes(self):
orig_value = self._get_local_settings_item(
self.project_name, self.local_project_settings_orig
)
self._set_current_text(orig_value)
def _show_actions(self):
if self.project_name is None:
return
menu = QtWidgets.QMenu(self)
actions_mapping = {}
if self.project_name == DEFAULT_PROJECT_KEY:
remove_label = LABEL_REMOVE_DEFAULT
add_label = LABEL_ADD_DEFAULT
else:
remove_label = LABEL_REMOVE_PROJECT
add_label = LABEL_ADD_PROJECT
has_value = self._get_local_settings_item(self.project_name)
if has_value:
action = QtWidgets.QAction(remove_label)
callback = self._remove_from_local
else:
action = QtWidgets.QAction(add_label)
callback = self._add_to_local
actions_mapping[action] = callback
menu.addAction(action)
if self.is_modified():
discard_changes_action = QtWidgets.QAction(LABEL_DISCARD_CHANGES)
actions_mapping[discard_changes_action] = self.discard_changes
menu.addAction(discard_changes_action)
result = menu.exec_(QtGui.QCursor.pos())
if result:
to_run = actions_mapping[result]
if to_run:
to_run()
def update_local_settings(self, local_project_settings):
self.local_project_settings = local_project_settings
self.local_project_settings_orig = copy.deepcopy(
dict(local_project_settings)
)
def current_text(self):
return self.combobox_input.currentText()
def change_project(self, project_name):
self.default_override_value = None
self.project_override_value = None
self.project_name = None
self.combobox_input.clear()
if project_name is None:
self._update_style()
return
is_default_project = bool(project_name == DEFAULT_PROJECT_KEY)
site_items = self._get_project_sites()
self.combobox_input.addItems(site_items)
default_item = self._get_local_settings_item(DEFAULT_PROJECT_KEY)
if is_default_project:
project_item = None
else:
project_item = self._get_local_settings_item(project_name)
index = None
if project_item:
idx = self.combobox_input.findText(project_item)
if idx >= 0:
self.project_override_value = project_item
index = idx
if default_item:
idx = self.combobox_input.findText(default_item)
if idx >= 0:
self.default_override_value = default_item
if index is None:
index = idx
if index is None:
settings_value = self._get_value_from_project_settings()
idx = self.combobox_input.findText(settings_value)
if idx >= 0:
index = idx
if index is not None:
self.combobox_input.setCurrentIndex(index)
self.project_name = project_name
self._update_style()
def _on_index_change(self):
if self.project_name is None:
return
self._set_local_settings_value(self.current_text())
self._update_style()
def _set_local_settings_value(self, value):
raise NotImplementedError(
"{} `_set_local_settings_value` not implemented".format(
self.__class__.__name__
)
)
def _get_project_sites(self):
raise NotImplementedError(
"{} `_get_project_sites` not implemented".format(
self.__class__.__name__
)
)
def _get_local_settings_item(self, project_name=None, data=None):
raise NotImplementedError(
"{}`_get_local_settings_item` not implemented".format(
self.__class__.__name__
)
)
def _get_value_from_project_settings(self):
raise NotImplementedError(
"{}`_get_value_from_project_settings` not implemented".format(
self.__class__.__name__
)
)
class AciveSiteCombo(_SiteCombobox):
input_label = "Active site"
def _get_project_sites(self):
return get_active_sites(self.project_settings)
def _get_local_settings_item(self, project_name=None, data=None):
if project_name is None:
project_name = self.project_name
if data is None:
data = self.local_project_settings
project_values = data.get(project_name)
value = None
if project_values:
value = project_values.get("active_site")
return value
def _get_value_from_project_settings(self):
global_entity = self.project_settings["project_settings"]["global"]
return global_entity["sync_server"]["config"]["active_site"].value
def _set_local_settings_value(self, value):
if self.project_name not in self.local_project_settings:
self.local_project_settings[self.project_name] = {}
self.local_project_settings[self.project_name]["active_site"] = value
class RemoteSiteCombo(_SiteCombobox):
input_label = "Remote site"
def _get_project_sites(self):
global_entity = self.project_settings["project_settings"]["global"]
sites_entity = global_entity["sync_server"]["sites"]
return tuple(sites_entity.keys())
def _get_local_settings_item(self, project_name=None, data=None):
if project_name is None:
project_name = self.project_name
if data is None:
data = self.local_project_settings
project_values = data.get(project_name)
value = None
if project_values:
value = project_values.get("remote_site")
return value
def _get_value_from_project_settings(self):
global_entity = self.project_settings["project_settings"]["global"]
return global_entity["sync_server"]["config"]["remote_site"].value
def _set_local_settings_value(self, value):
if self.project_name not in self.local_project_settings:
self.local_project_settings[self.project_name] = {}
self.local_project_settings[self.project_name]["remote_site"] = value
class RootSiteWidget(QtWidgets.QWidget):
def __init__(self, project_settings, parent):
self._parent_widget = parent
super(RootSiteWidget, self).__init__(parent)
self.project_settings = project_settings
self._project_name = None
sites_widget = QtWidgets.QWidget(self)
active_site_widget = AciveSiteCombo(project_settings, sites_widget)
remote_site_widget = RemoteSiteCombo(project_settings, sites_widget)
sites_layout = QtWidgets.QHBoxLayout(sites_widget)
sites_layout.setContentsMargins(0, 0, 0, 0)
sites_layout.addWidget(active_site_widget)
sites_layout.addWidget(remote_site_widget)
sites_layout.addWidget(SpacerWidget(self), 1)
roots_widget = RootsWidget(project_settings, self)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(sites_widget)
main_layout.addWidget(roots_widget)
main_layout.addWidget(SpacerWidget(self), 1)
self.active_site_widget = active_site_widget
self.remote_site_widget = remote_site_widget
self.roots_widget = roots_widget
def _active_site_values(self):
global_entity = self.project_settings["project_settings"]["global"]
sites_entity = global_entity["sync_server"]["sites"]
return tuple(sites_entity.keys())
def _remote_site_values(self):
global_entity = self.project_settings["project_settings"]["global"]
sites_entity = global_entity["sync_server"]["sites"]
return tuple(sites_entity.keys())
def update_local_settings(self, local_project_settings):
self.local_project_settings = local_project_settings
self.active_site_widget.update_local_settings(local_project_settings)
self.remote_site_widget.update_local_settings(local_project_settings)
self.roots_widget.update_local_settings(local_project_settings)
project_name = self._project_name
if project_name is None:
project_name = DEFAULT_PROJECT_KEY
self.change_project(project_name)
def change_project(self, project_name):
self._project_name = project_name
# Set roots project to None so all changes below are ignored
self.roots_widget.change_project(None)
# Aply changes in site comboboxes
self.active_site_widget.change_project(project_name)
self.remote_site_widget.change_project(project_name)
# Change project name in roots widget
self.roots_widget.change_project(project_name)
class ProjectValue(dict):
pass
class ProjectSettingsWidget(QtWidgets.QWidget):
def __init__(self, project_settings, parent):
super(ProjectSettingsWidget, self).__init__(parent)
self.local_project_settings = {}
projects_widget = _ProjectListWidget(self)
roos_site_widget = RootSiteWidget(project_settings, self)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(projects_widget, 0)
main_layout.addWidget(roos_site_widget, 1)
projects_widget.project_changed.connect(self._on_project_change)
self.project_settings = project_settings
self.projects_widget = projects_widget
self.roos_site_widget = roos_site_widget
def project_name(self):
return self.projects_widget.project_name()
def _on_project_change(self):
project_name = self.project_name()
self.project_settings.change_project(project_name)
if project_name is None:
project_name = DEFAULT_PROJECT_KEY
self.roos_site_widget.change_project(project_name)
def update_local_settings(self, value):
if not value:
value = {}
self.local_project_settings = ProjectValue(value)
self.roos_site_widget.update_local_settings(
self.local_project_settings
)
self.projects_widget.refresh()
def _clear_value(self, value):
if not value:
return None
if not isinstance(value, dict):
return value
output = {}
for _key, _value in value.items():
_modified_value = self._clear_value(_value)
if _modified_value:
output[_key] = _modified_value
return output
def settings_value(self):
output = self._clear_value(self.local_project_settings)
if not output:
return None
return output

View file

@ -0,0 +1,59 @@
from Qt import QtWidgets, QtCore
from pype.tools.settings.settings.widgets.widgets import (
ExpandingWidget,
SpacerWidget
)
class Separator(QtWidgets.QFrame):
def __init__(self, height=None, parent=None):
super(Separator, self).__init__(parent)
if height is None:
height = 2
splitter_item = QtWidgets.QWidget(self)
splitter_item.setStyleSheet("background-color: #21252B;")
splitter_item.setMinimumHeight(height)
splitter_item.setMaximumHeight(height)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(5, 5, 5, 5)
layout.addWidget(splitter_item)
class ProxyLabelWidget(QtWidgets.QWidget):
def __init__(self, label, mouse_release_callback, parent=None):
super(ProxyLabelWidget, self).__init__(parent)
self.mouse_release_callback = mouse_release_callback
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
label_widget = QtWidgets.QLabel(label, self)
layout.addWidget(label_widget)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_widget = label_widget
def setText(self, text):
self.label_widget.setText(text)
def set_label_property(self, *args, **kwargs):
self.label_widget.setProperty(*args, **kwargs)
self.label_widget.style().polish(self.label_widget)
def mouseReleaseEvent(self, event):
if self.mouse_release_callback:
return self.mouse_release_callback(event)
return super(ProxyLabelWidget, self).mouseReleaseEvent(event)
__all__ = (
"ExpandingWidget",
"SpacerWidget",
"Separator",
"SpacerWidget"
)

View file

@ -0,0 +1,204 @@
import logging
from Qt import QtWidgets, QtGui
from ..settings import style
from pype.settings.lib import (
get_local_settings,
save_local_settings
)
from pype.api import (
SystemSettings,
ProjectSettings
)
from .widgets import (
SpacerWidget,
ExpandingWidget
)
from .mongo_widget import PypeMongoWidget
from .general_widget import LocalGeneralWidgets
from .apps_widget import LocalApplicationsWidgets
from .projects_widget import ProjectSettingsWidget
from .constants import (
CHILD_OFFSET,
LOCAL_GENERAL_KEY,
LOCAL_PROJECTS_KEY,
LOCAL_APPS_KEY
)
log = logging.getLogger(__name__)
class LocalSettingsWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(LocalSettingsWidget, self).__init__(parent)
self.system_settings = SystemSettings()
self.project_settings = ProjectSettings()
self.main_layout = QtWidgets.QVBoxLayout(self)
self.pype_mongo_widget = None
self.general_widget = None
self.apps_widget = None
self.projects_widget = None
self._create_pype_mongo_ui()
self._create_general_ui()
self._create_app_ui()
self._create_project_ui()
# Add spacer to main layout
self.main_layout.addWidget(SpacerWidget(self), 1)
def _create_pype_mongo_ui(self):
pype_mongo_expand_widget = ExpandingWidget("Pype Mongo URL", self)
pype_mongo_content = QtWidgets.QWidget(self)
pype_mongo_layout = QtWidgets.QVBoxLayout(pype_mongo_content)
pype_mongo_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
pype_mongo_expand_widget.set_content_widget(pype_mongo_content)
pype_mongo_widget = PypeMongoWidget(self)
pype_mongo_layout.addWidget(pype_mongo_widget)
self.main_layout.addWidget(pype_mongo_expand_widget)
self.pype_mongo_widget = pype_mongo_widget
def _create_general_ui(self):
# General
general_expand_widget = ExpandingWidget("General", self)
general_content = QtWidgets.QWidget(self)
general_layout = QtWidgets.QVBoxLayout(general_content)
general_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
general_expand_widget.set_content_widget(general_content)
general_widget = LocalGeneralWidgets(general_content)
general_layout.addWidget(general_widget)
self.main_layout.addWidget(general_expand_widget)
self.general_widget = general_widget
def _create_app_ui(self):
# Applications
app_expand_widget = ExpandingWidget("Applications", self)
app_content = QtWidgets.QWidget(self)
app_layout = QtWidgets.QVBoxLayout(app_content)
app_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
app_expand_widget.set_content_widget(app_content)
app_widget = LocalApplicationsWidgets(
self.system_settings, app_content
)
app_layout.addWidget(app_widget)
self.main_layout.addWidget(app_expand_widget)
self.app_widget = app_widget
def _create_project_ui(self):
project_expand_widget = ExpandingWidget("Project settings", self)
project_content = QtWidgets.QWidget(self)
project_layout = QtWidgets.QVBoxLayout(project_content)
project_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
project_expand_widget.set_content_widget(project_content)
projects_widget = ProjectSettingsWidget(self.project_settings, self)
project_layout.addWidget(projects_widget)
self.main_layout.addWidget(project_expand_widget)
self.projects_widget = projects_widget
def update_local_settings(self, value):
if not value:
value = {}
self.system_settings.reset()
self.project_settings.reset()
self.general_widget.update_local_settings(
value.get(LOCAL_GENERAL_KEY)
)
self.app_widget.update_local_settings(
value.get(LOCAL_APPS_KEY)
)
self.projects_widget.update_local_settings(
value.get(LOCAL_PROJECTS_KEY)
)
def settings_value(self):
output = {}
general_value = self.general_widget.settings_value()
if general_value:
output[LOCAL_GENERAL_KEY] = general_value
app_value = self.app_widget.settings_value()
if app_value:
output[LOCAL_APPS_KEY] = app_value
projects_value = self.projects_widget.settings_value()
if projects_value:
output[LOCAL_PROJECTS_KEY] = projects_value
return output
class LocalSettingsWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(LocalSettingsWindow, self).__init__(parent)
self.resize(1000, 600)
self.setWindowTitle("Pype Local settings")
stylesheet = style.load_stylesheet()
self.setStyleSheet(stylesheet)
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
settings_widget = LocalSettingsWidget(scroll_widget)
scroll_widget.setWidget(settings_widget)
scroll_widget.setWidgetResizable(True)
footer = QtWidgets.QWidget(self)
save_btn = QtWidgets.QPushButton("Save", footer)
reset_btn = QtWidgets.QPushButton("Reset", footer)
footer_layout = QtWidgets.QHBoxLayout(footer)
footer_layout.addWidget(reset_btn, 0)
footer_layout.addWidget(SpacerWidget(footer), 1)
footer_layout.addWidget(save_btn, 0)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(scroll_widget, 1)
main_layout.addWidget(footer, 0)
save_btn.clicked.connect(self._on_save_clicked)
reset_btn.clicked.connect(self._on_reset_clicked)
self.settings_widget = settings_widget
self.reset_btn = reset_btn
self.save_btn = save_btn
self.reset()
def reset(self):
value = get_local_settings()
self.settings_widget.update_local_settings(value)
def _on_reset_clicked(self):
self.reset()
def _on_save_clicked(self):
value = self.settings_widget.settings_value()
save_local_settings(value)
self.reset()

View file

@ -664,9 +664,8 @@ class ProjectListWidget(QtWidgets.QWidget):
self.current_project = None
if self.dbcon:
for project_doc in tuple(self.dbcon.projects()):
items.append(project_doc["name"])
for project_name in self.dbcon.database.collection_names():
items.append(project_name)
for item in items:
model.appendRow(QtGui.QStandardItem(item))