diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 02ae9635c1..12c04a4236 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -97,7 +97,8 @@ from .local_settings import ( OpenPypeSettingsRegistry, get_local_site_id, change_openpype_mongo_url, - get_openpype_username + get_openpype_username, + is_admin_password_required ) from .applications import ( @@ -209,6 +210,7 @@ __all__ = [ "get_local_site_id", "change_openpype_mongo_url", "get_openpype_username", + "is_admin_password_required", "ApplicationLaunchFailed", "ApplictionExecutableNotFound", diff --git a/openpype/lib/local_settings.py b/openpype/lib/local_settings.py index 67845c77cf..66dad279de 100644 --- a/openpype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -29,7 +29,10 @@ except ImportError: import six import appdirs -from openpype.settings import get_local_settings +from openpype.settings import ( + get_local_settings, + get_system_settings +) from .import validate_mongo_connection @@ -562,3 +565,16 @@ def get_openpype_username(): if not username: username = getpass.getuser() return username + + +def is_admin_password_required(): + system_settings = get_system_settings() + password = system_settings["general"].get("admin_password") + if not password: + return False + + local_settings = get_local_settings() + is_admin = local_settings.get("general", {}).get("is_admin", False) + if is_admin: + return False + return True diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index bae48c540b..debeeed6bf 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -36,6 +36,7 @@ from .clockify import ClockifyModule from .log_viewer import LogViewModule from .muster import MusterModule from .deadline import DeadlineModule +from .project_manager_action import ProjectManagerAction from .standalonepublish_action import StandAlonePublishAction from .sync_server import SyncServerModule @@ -73,6 +74,7 @@ __all__ = ( "LogViewModule", "MusterModule", "DeadlineModule", + "ProjectManagerAction", "StandAlonePublishAction", "SyncServerModule" diff --git a/openpype/modules/avalon_apps/avalon_app.py b/openpype/modules/avalon_apps/avalon_app.py index 243c4f928a..4e95f6e72b 100644 --- a/openpype/modules/avalon_apps/avalon_app.py +++ b/openpype/modules/avalon_apps/avalon_app.py @@ -86,7 +86,7 @@ class AvalonModule(PypeModule, ITrayModule, IWebServerRoutes): from Qt import QtWidgets # Actions action_library_loader = QtWidgets.QAction( - "Library loader", tray_menu + "Loader", tray_menu ) action_library_loader.triggered.connect(self.show_library_loader) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 441a9731b7..44368d77a7 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -184,6 +184,9 @@ class ITrayAction(ITrayModule): necessary. """ + admin_action = False + _admin_submenu = None + @property @abstractmethod def label(self): @@ -197,9 +200,19 @@ class ITrayAction(ITrayModule): def tray_menu(self, tray_menu): from Qt import QtWidgets - action = QtWidgets.QAction(self.label, tray_menu) + + if self.admin_action: + menu = self.admin_submenu(tray_menu) + action = QtWidgets.QAction(self.label, menu) + menu.addAction(action) + if not menu.menuAction().isVisible(): + menu.menuAction().setVisible(True) + + else: + action = QtWidgets.QAction(self.label, tray_menu) + tray_menu.addAction(action) + action.triggered.connect(self.on_action_trigger) - tray_menu.addAction(action) def tray_start(self): return @@ -207,6 +220,16 @@ class ITrayAction(ITrayModule): def tray_exit(self): return + @staticmethod + def admin_submenu(tray_menu): + if ITrayAction._admin_submenu is None: + from Qt import QtWidgets + + admin_submenu = QtWidgets.QMenu("Admin", tray_menu) + admin_submenu.menuAction().setVisible(False) + ITrayAction._admin_submenu = admin_submenu + return ITrayAction._admin_submenu + class ITrayService(ITrayModule): # Module's property @@ -233,6 +256,7 @@ class ITrayService(ITrayModule): def services_submenu(tray_menu): if ITrayService._services_submenu is None: from Qt import QtWidgets + services_submenu = QtWidgets.QMenu("Services", tray_menu) services_submenu.menuAction().setVisible(False) ITrayService._services_submenu = services_submenu diff --git a/openpype/modules/project_manager_action.py b/openpype/modules/project_manager_action.py new file mode 100644 index 0000000000..1387aa258c --- /dev/null +++ b/openpype/modules/project_manager_action.py @@ -0,0 +1,59 @@ +from . import PypeModule, ITrayAction + + +class ProjectManagerAction(PypeModule, ITrayAction): + label = "Project Manager (beta)" + name = "project_manager" + admin_action = True + + def initialize(self, modules_settings): + enabled = False + module_settings = modules_settings.get(self.name) + if module_settings: + enabled = module_settings.get("enabled", enabled) + self.enabled = enabled + + # Tray attributes + self.project_manager_window = None + + def connect_with_modules(self, *_a, **_kw): + return + + def tray_init(self): + """Initialization in tray implementation of ITrayAction.""" + self.create_project_manager_window() + + def on_action_trigger(self): + """Implementation for action trigger of ITrayAction.""" + self.show_project_manager_window() + + def create_project_manager_window(self): + """Initializa Settings Qt window.""" + if self.project_manager_window: + return + from openpype.tools.project_manager import ProjectManagerWindow + + self.project_manager_window = ProjectManagerWindow() + + def show_project_manager_window(self): + """Show project manager tool window. + + Raises: + AssertionError: Window must be already created. Call + `create_project_manager_window` before calling this method. + """ + if not self.project_manager_window: + raise AssertionError("Window is not initialized.") + + # Store if was visible + was_minimized = self.project_manager_window.isMinimized() + + # Show settings gui + self.project_manager_window.show() + + if was_minimized: + self.project_manager_window.showNormal() + + # Pull window to the front. + self.project_manager_window.raise_() + self.project_manager_window.activateWindow() diff --git a/openpype/modules/settings_action.py b/openpype/modules/settings_action.py index 8a3ee4792a..1035dc0dcd 100644 --- a/openpype/modules/settings_action.py +++ b/openpype/modules/settings_action.py @@ -37,7 +37,8 @@ class ISettingsChangeListener: class SettingsAction(PypeModule, ITrayAction): """Action to show Setttings tool.""" name = "settings" - label = "Settings" + label = "Studio Settings" + admin_action = True def initialize(self, _modules_settings): # This action is always enabled @@ -78,7 +79,7 @@ class SettingsAction(PypeModule, ITrayAction): Raises: AssertionError: Window must be already created. Call - `create_settings_window` before callint this method. + `create_settings_window` before calling this method. """ if not self.settings_window: raise AssertionError("Window is not initialized.") @@ -105,7 +106,7 @@ class SettingsAction(PypeModule, ITrayAction): class LocalSettingsAction(PypeModule, ITrayAction): """Action to show Setttings tool.""" name = "local_settings" - label = "Local Settings" + label = "Settings" def initialize(self, _modules_settings): # This action is always enabled diff --git a/openpype/tools/settings/resources/images/eye.png b/openpype/resources/icons/eye.png similarity index 100% rename from openpype/tools/settings/resources/images/eye.png rename to openpype/resources/icons/eye.png diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 5c4aa6c485..31da9e9e7b 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -164,5 +164,8 @@ }, "standalonepublish_tool": { "enabled": true + }, + "project_manager": { + "enabled": true } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 16251b5f27..d6527f368d 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -192,6 +192,20 @@ "label": "Enabled" } ] + }, + { + "type": "dict", + "key": "project_manager", + "label": "Project Manager (beta)", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] } ] } diff --git a/openpype/style/style.css b/openpype/style/style.css index 7db3558c14..aa71105320 100644 --- a/openpype/style/style.css +++ b/openpype/style/style.css @@ -504,6 +504,21 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { #IconBtn {} +/* Password dialog*/ +#PasswordBtn { + border: none; + padding:0.1em; + background: transparent; +} + +#PasswordBtn:hover { + background: {color:bg-buttons}; +} + +#RememberCheckbox { + spacing: 0.5em; +} + /* Project Manager stylesheets */ #HierarchyView::item { padding-top: 3px; diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 83184f9981..37092bc4a9 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -11,6 +11,8 @@ from . import ( ) from openpype.style import load_stylesheet from .style import ResourceCache +from openpype.lib import is_admin_password_required +from openpype.widgets import PasswordDialog from openpype import resources from avalon.api import AvalonMongoDB @@ -20,6 +22,10 @@ class ProjectManagerWindow(QtWidgets.QWidget): def __init__(self, parent=None): super(ProjectManagerWindow, self).__init__(parent) + self._initial_reset = False + self._password_dialog = None + self._user_passed = False + self.setWindowTitle("OpenPype Project Manager") self.setWindowIcon(QtGui.QIcon(resources.pype_icon_filepath())) @@ -33,6 +39,9 @@ class ProjectManagerWindow(QtWidgets.QWidget): project_model = ProjectModel(dbcon) project_combobox = QtWidgets.QComboBox(project_widget) + project_combobox.setSizeAdjustPolicy( + QtWidgets.QComboBox.AdjustToContents + ) project_combobox.setModel(project_model) project_combobox.setRootModelIndex(QtCore.QModelIndex()) style_delegate = QtWidgets.QStyledItemDelegate() @@ -135,13 +144,15 @@ class ProjectManagerWindow(QtWidgets.QWidget): self.resize(1200, 600) self.setStyleSheet(load_stylesheet()) - self.refresh_projects() - def _set_project(self, project_name=None): self.hierarchy_view.set_project(project_name) def showEvent(self, event): super(ProjectManagerWindow, self).showEvent(event) + + if not self._initial_reset: + self.reset() + font_size = self._refresh_projects_btn.fontMetrics().height() icon_size = QtCore.QSize(font_size, font_size) self._refresh_projects_btn.setIconSize(icon_size) @@ -193,3 +204,45 @@ class ProjectManagerWindow(QtWidgets.QWidget): project_name = dialog.project_name self.show_message("Created project \"{}\"".format(project_name)) self.refresh_projects(project_name) + + def _show_password_dialog(self): + if self._password_dialog: + self._password_dialog.open() + + def _on_password_dialog_close(self, password_passed): + # Store result for future settings reset + self._user_passed = password_passed + # Remove reference to password dialog + self._password_dialog = None + if password_passed: + self.reset() + else: + self.close() + + def reset(self): + if self._password_dialog: + return + + if not self._user_passed: + self._user_passed = not is_admin_password_required() + + if not self._user_passed: + self.setEnabled(False) + # Avoid doubled dialog + dialog = PasswordDialog(self) + dialog.setModal(True) + dialog.finished.connect(self._on_password_dialog_close) + + self._password_dialog = dialog + + QtCore.QTimer.singleShot(100, self._show_password_dialog) + + return + + self.setEnabled(True) + + # Mark as was reset + if not self._initial_reset: + self._initial_reset = True + + self.refresh_projects() diff --git a/openpype/tools/settings/__init__.py b/openpype/tools/settings/__init__.py index 1a4752fe54..a156228dc1 100644 --- a/openpype/tools/settings/__init__.py +++ b/openpype/tools/settings/__init__.py @@ -1,11 +1,9 @@ import sys from Qt import QtWidgets, QtGui from .lib import ( - is_password_required, BTN_FIXED_SIZE, CHILD_OFFSET ) -from .widgets import PasswordDialog from .local_settings import LocalSettingsWindow from .settings import ( style, @@ -35,13 +33,11 @@ def main(user_role=None): __all__ = ( - "is_password_required", "BTN_FIXED_SIZE", "CHILD_OFFSET", "style", - "PasswordDialog", "MainWidget", "ProjectListWidget", "LocalSettingsWindow", diff --git a/openpype/tools/settings/lib.py b/openpype/tools/settings/lib.py index 4b48746a18..9520e268dd 100644 --- a/openpype/tools/settings/lib.py +++ b/openpype/tools/settings/lib.py @@ -1,20 +1,2 @@ CHILD_OFFSET = 15 BTN_FIXED_SIZE = 20 - - -def is_password_required(): - from openpype.settings import ( - get_system_settings, - get_local_settings - ) - - system_settings = get_system_settings() - password = system_settings["general"].get("admin_password") - if not password: - return False - - local_settings = get_local_settings() - is_admin = local_settings.get("general", {}).get("is_admin", False) - if is_admin: - return False - return True diff --git a/openpype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py index d01c16ff82..5bb2bcf378 100644 --- a/openpype/tools/settings/local_settings/general_widget.py +++ b/openpype/tools/settings/local_settings/general_widget.py @@ -1,10 +1,8 @@ import getpass from Qt import QtWidgets, QtCore -from openpype.tools.settings import ( - is_password_required, - PasswordDialog -) +from openpype.lib import is_admin_password_required +from openpype.widgets import PasswordDialog class LocalGeneralWidgets(QtWidgets.QWidget): @@ -57,7 +55,7 @@ class LocalGeneralWidgets(QtWidgets.QWidget): if not self.is_admin_input.isChecked(): return - if not is_password_required(): + if not is_admin_password_required(): return dialog = PasswordDialog(self, False) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index bddac7e98b..a60a2a1d88 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -7,10 +7,8 @@ from .categories import ( from .widgets import ShadowWidget, RestartDialog from . import style -from openpype.tools.settings import ( - is_password_required, - PasswordDialog -) +from openpype.lib import is_admin_password_required +from openpype.widgets import PasswordDialog class MainWidget(QtWidgets.QWidget): @@ -117,7 +115,7 @@ class MainWidget(QtWidgets.QWidget): return if not self._user_passed: - self._user_passed = not is_password_required() + self._user_passed = not is_admin_password_required() self._on_state_change() diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 2c739e3e39..0272ba869b 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -15,7 +15,11 @@ from openpype.api import ( get_system_settings ) from openpype.lib import get_pype_execute_args -from openpype.modules import TrayModulesManager, ITrayService +from openpype.modules import ( + TrayModulesManager, + ITrayAction, + ITrayService +) from openpype import style from .pype_info_widget import PypeInfoWidget @@ -67,6 +71,9 @@ class TrayManager: self.modules_manager.initialize(self, self.tray_widget.menu) + admin_submenu = ITrayAction.admin_submenu(self.tray_widget.menu) + self.tray_widget.menu.addMenu(admin_submenu) + # Add services if they are services_submenu = ITrayService.services_submenu(self.tray_widget.menu) self.tray_widget.menu.addMenu(services_submenu) diff --git a/openpype/widgets/__init__.py b/openpype/widgets/__init__.py index e69de29bb2..b0552c7a0d 100644 --- a/openpype/widgets/__init__.py +++ b/openpype/widgets/__init__.py @@ -0,0 +1,6 @@ +from .password_dialog import PasswordDialog + + +__all__ = ( + "PasswordDialog", +) diff --git a/openpype/tools/settings/widgets.py b/openpype/widgets/password_dialog.py similarity index 93% rename from openpype/tools/settings/widgets.py rename to openpype/widgets/password_dialog.py index e2662f350f..9990642ca1 100644 --- a/openpype/tools/settings/widgets.py +++ b/openpype/widgets/password_dialog.py @@ -1,6 +1,7 @@ from Qt import QtWidgets, QtCore, QtGui -from .resources import get_resource +from openpype import style +from openpype.resources import get_resource from openpype.api import get_system_settings from openpype.settings.lib import ( @@ -43,7 +44,7 @@ class PasswordDialog(QtWidgets.QDialog): def __init__(self, parent=None, allow_remember=True): super(PasswordDialog, self).__init__(parent) - self.setWindowTitle("Settings Password") + self.setWindowTitle("Admin Password") self.resize(300, 120) system_settings = get_system_settings() @@ -62,13 +63,11 @@ class PasswordDialog(QtWidgets.QDialog): password_input = QtWidgets.QLineEdit(password_widget) password_input.setEchoMode(QtWidgets.QLineEdit.Password) - show_password_icon_path = get_resource("images", "eye.png") + show_password_icon_path = get_resource("icons", "eye.png") show_password_icon = QtGui.QIcon(show_password_icon_path) show_password_btn = PressHoverButton(password_widget) + show_password_btn.setObjectName("PasswordBtn") show_password_btn.setIcon(show_password_icon) - show_password_btn.setStyleSheet(( - "border: none;padding:0.1em;" - )) show_password_btn.setFocusPolicy(QtCore.Qt.ClickFocus) password_layout = QtWidgets.QHBoxLayout(password_widget) @@ -83,10 +82,8 @@ class PasswordDialog(QtWidgets.QDialog): buttons_widget = QtWidgets.QWidget(self) remember_checkbox = QtWidgets.QCheckBox("Remember", buttons_widget) + remember_checkbox.setObjectName("RememberCheckbox") remember_checkbox.setVisible(allow_remember) - remember_checkbox.setStyleSheet(( - "spacing: 0.5em;" - )) ok_btn = QtWidgets.QPushButton("Ok", buttons_widget) cancel_btn = QtWidgets.QPushButton("Cancel", buttons_widget) @@ -114,6 +111,8 @@ class PasswordDialog(QtWidgets.QDialog): self.remember_checkbox = remember_checkbox self.message_label = message_label + self.setStyleSheet(style.load_stylesheet()) + def remember_password(self): if not self._allow_remember: return False