From 2580f01646feb58658b04c951901fbd47de13288 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 15:28:10 +0800 Subject: [PATCH 01/28] Implement eventFilter removal, eased #296 --- pyblish_qml/host.py | 6 ++++-- pyblish_qml/ipc/server.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 1c62f56d..7833d05d 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -281,11 +281,13 @@ def install_event_filter(): raise Exception("Main window not found, event filter did not " "install. This is a bug.") + event_filter = _state.get("eventFilter", HostEventFilter(main_window)) try: - host_event_filter = HostEventFilter(main_window) - main_window.installEventFilter(host_event_filter) + main_window.installEventFilter(event_filter) except Exception: pass + else: + _state["eventFilter"] = event_filter def _on_application_quit(): diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 975ddf8c..6ddd5d42 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -119,6 +119,20 @@ def publish(self): def validate(self): return self._dispatch("validate") + def _remove_event_filter(self): + event_filter = _state.get("eventFilter") + if isinstance(event_filter, QtCore.QObject): + + # (NOTE) Should remove from the QApp instance which originally + # installed to. + # This will not work: + # `QApplication.instance().removeEventFilter(event_filter)` + # + event_filter.parent().removeEventFilter(event_filter) + del _state["eventFilter"] + + print("The eventFilter of pyblish-qml has been removed.") + def _alive(self): """Send pulse to child process @@ -169,6 +183,7 @@ def _flush(self, data): except IOError: # subprocess closed self.vessel.close() + self._remove_event_filter() else: return True From b854b3d3c896a1cf16448f8601732296c5ab38ff Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 17:41:30 +0800 Subject: [PATCH 02/28] Reinstall eventFilter if removed --- pyblish_qml/host.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 7833d05d..34326f54 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -177,6 +177,9 @@ def on_shown(): server.listen() + # Install eventFilter if not exists one + install_event_filter() + return server @@ -278,16 +281,24 @@ def install_event_filter(): main_window = _state.get("vesselParent") if main_window is None: - raise Exception("Main window not found, event filter did not " - "install. This is a bug.") + print("Main window not found, event filter did not install.") + return + + event_filter = _state.get("eventFilter") + if isinstance(event_filter, QtCore.QObject): + print("Event filter exists.") + return + else: + event_filter = HostEventFilter(main_window) - event_filter = _state.get("eventFilter", HostEventFilter(main_window)) try: main_window.installEventFilter(event_filter) - except Exception: - pass + except Exception as e: + print("An error has occurred during event filter's installation.") + print(e) else: _state["eventFilter"] = event_filter + print("Event filter has been installed.") def _on_application_quit(): @@ -415,8 +426,6 @@ def threaded_wrapper(func, *args, **kwargs): for widget in QtWidgets.QApplication.topLevelWidgets() }["MayaWindow"] - install_event_filter() - _set_host_label("Maya") @@ -431,8 +440,6 @@ def _common_setup(host_name, threaded_wrapper, use_threaded_wrapper): app.aboutToQuit.connect(_on_application_quit) _acquire_host_main_window(app) - install_event_filter() - _set_host_label(host_name) From 607a92adb47949710cf1508cfeaf03b945136f52 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 18:15:20 +0800 Subject: [PATCH 03/28] Fix buggy window state --- pyblish_qml/app.py | 4 ++-- pyblish_qml/ipc/server.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 577dd2f0..0ca0b0d1 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -232,11 +232,11 @@ def hide(self): via a call to `show()` """ - self.vessel.hide() + self.window.hide() def rise(self): """Rise GUI from hidden""" - self.vessel.show() + self.window.show() def inFocus(self): """Set GUI on-top flag""" diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 6ddd5d42..73eab9c4 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -93,6 +93,7 @@ def quit(self): def rise(self): """Rise GUI from hidden""" + self.vessel.show() self._dispatch("rise") def inFocus(self): From 17e915d88b2b80de1e04fe9c1f923b073186c439 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 20:54:42 +0800 Subject: [PATCH 04/28] Improve foster mode stability --- pyblish_qml/app.py | 35 ++++++++++++++--------------------- pyblish_qml/ipc/client.py | 3 +++ pyblish_qml/ipc/server.py | 5 ++++- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 0ca0b0d1..9c9ebf9d 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -268,16 +268,6 @@ def _popup(self): window.setFlags(previous_flags | QtCore.Qt.WindowStaysOnTopHint) window.setFlags(previous_flags) - def _set_goemetry(self, source, target): - """Set window position and size after parent swap""" - target.setFramePosition(source.framePosition()) - - window_state = source.windowState() - target.setWindowState(window_state) - - if not window_state == QtCore.Qt.WindowMaximized: - target.resize(source.size()) - def detach(self): """Detach QQuickView window from the host @@ -297,14 +287,15 @@ def detach(self): print("Detach window from foster parent...") self.vessel = self.native_vessel - - self.host.detach() - self.fostered = False - self.vessel.show() - self.window.setParent(self.vessel) - self._set_goemetry(self.foster_vessel, self.vessel) + self.window.setParent(self.native_vessel) + # Show dst container + self.native_vessel.show() + self.native_vessel.setGeometry(self.foster_vessel.geometry()) + # Hide src container + self.host.detach() + # Stay on top self._popup() self.controller.detached.emit() @@ -328,14 +319,16 @@ def attach(self): print("Attach window to foster parent...") self.vessel = self.foster_vessel + self.fostered = True + self.window.setParent(self.foster_vessel) + # Show dst container self.host.attach() - + self.foster_vessel.setGeometry(self.native_vessel.geometry()) + # Hide src container self.native_vessel.hide() - self.fostered = True - - self.window.setParent(self.vessel) - self._set_goemetry(self.native_vessel, self.vessel) + # Stay on top + self.host.popup() self.controller.attached.emit() diff --git a/pyblish_qml/ipc/client.py b/pyblish_qml/ipc/client.py index 4bfb0ab1..f92ffce6 100644 --- a/pyblish_qml/ipc/client.py +++ b/pyblish_qml/ipc/client.py @@ -39,6 +39,9 @@ def detach(self): def attach(self): self._dispatch("attach") + def popup(self): + self._dispatch("popup") + def test(self, **vars): """Vars can only be passed as a non-keyword argument""" return self._dispatch("test", kwargs=vars) diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 73eab9c4..ddf32aaf 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -114,6 +114,9 @@ def detach(self): def attach(self): self.vessel.show() + def popup(self): + self.vessel.activateWindow() # to top + def publish(self): return self._dispatch("publish") @@ -349,7 +352,7 @@ def _listen(): # self.service have no access to proxy object, so # this `if` statement is needed - if func_name in ("detach", "attach"): + if func_name in ("detach", "attach", "popup"): getattr(self.proxy, func_name)() result = None From 577e8faaa90ffd24c51d8c52835c2dfc3a65e792 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 21:37:58 +0800 Subject: [PATCH 05/28] Reinforce stay-on-top when detaching on foster mode --- pyblish_qml/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 9c9ebf9d..be85b386 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -296,6 +296,7 @@ def detach(self): # Hide src container self.host.detach() # Stay on top + self.window.requestActivate() self._popup() self.controller.detached.emit() From a3227fdc1c98959c1cb1a00f8f7780da9cc740e9 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Thu, 30 Aug 2018 23:47:28 +0800 Subject: [PATCH 06/28] Foster mode stability, wait host during detach-attach --- pyblish_qml/app.py | 20 ++++++++++++++++---- pyblish_qml/ipc/client.py | 5 +++-- pyblish_qml/ipc/server.py | 11 ++++++++--- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index be85b386..96d6bb40 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -88,6 +88,8 @@ class Application(QtGui.QGuiApplication): attached = QtCore.pyqtSignal() detached = QtCore.pyqtSignal() + host_attached = QtCore.pyqtSignal() + host_detached = QtCore.pyqtSignal() def __init__(self, source, targets=[]): super(Application, self).__init__(sys.argv) @@ -194,6 +196,9 @@ def show(self, client_settings=None, window_id=None): ) ) + if self.fostered: + self.native_vessel.setTitle(client_settings["WindowTitle"]) + message = list() message.append("Settings: ") for key, value in settings.to_dict().items(): @@ -291,10 +296,13 @@ def detach(self): self.window.setParent(self.native_vessel) # Show dst container + self.native_vessel.setOpacity(100) self.native_vessel.show() self.native_vessel.setGeometry(self.foster_vessel.geometry()) - # Hide src container + # Hide src container (will wait for host) + host_detached = QtTest.QSignalSpy(self.host_detached) self.host.detach() + host_detached.wait(300) # Stay on top self.window.requestActivate() self._popup() @@ -323,10 +331,12 @@ def attach(self): self.fostered = True self.window.setParent(self.foster_vessel) - # Show dst container - self.host.attach() - self.foster_vessel.setGeometry(self.native_vessel.geometry()) + # Show dst container (will wait for host) + host_attached = QtTest.QSignalSpy(self.host_attached) + self.host.attach(self.native_vessel.geometry()) + host_attached.wait(300) # Hide src container + self.native_vessel.setOpacity(0) # avoid hide window anim self.native_vessel.hide() # Stay on top self.host.popup() @@ -375,6 +385,8 @@ def _listen(): "attach": "attached", "detach": "detached", + "host_attach": "host_attached", + "host_detach": "host_detached", }.get(payload["name"]) diff --git a/pyblish_qml/ipc/client.py b/pyblish_qml/ipc/client.py index f92ffce6..4ec45258 100644 --- a/pyblish_qml/ipc/client.py +++ b/pyblish_qml/ipc/client.py @@ -36,8 +36,9 @@ def reset(self): def detach(self): self._dispatch("detach") - def attach(self): - self._dispatch("attach") + def attach(self, qRect): + geometry = [qRect.x(), qRect.y(), qRect.width(), qRect.height()] + self._dispatch("attach", args=geometry) def popup(self): self._dispatch("popup") diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index ddf32aaf..cc798d6f 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -109,10 +109,15 @@ def kill(self): self.popen.kill() def detach(self): + self.vessel.setWindowOpacity(0) # avoid hide window anim self.vessel.hide() + self._dispatch("host_detach") - def attach(self): + def attach(self, x, y, w, h): + self.vessel.setWindowOpacity(100) self.vessel.show() + self.vessel.setGeometry(x, y, w, h) + self._dispatch("host_attach") def popup(self): self.vessel.activateWindow() # to top @@ -135,7 +140,7 @@ def _remove_event_filter(self): event_filter.parent().removeEventFilter(event_filter) del _state["eventFilter"] - print("The eventFilter of pyblish-qml has been removed.") + print("The eventFilter of pyblish-qml has been removed.\n") def _alive(self): """Send pulse to child process @@ -353,7 +358,7 @@ def _listen(): # self.service have no access to proxy object, so # this `if` statement is needed if func_name in ("detach", "attach", "popup"): - getattr(self.proxy, func_name)() + getattr(self.proxy, func_name)(*args) result = None else: From 7a4ebf51be4ed49489b4e0f1100ae02fe02ea5d2 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Fri, 31 Aug 2018 19:03:59 +0800 Subject: [PATCH 07/28] Fix buggy `pyblishQmlClose` signal emission --- pyblish_qml/app.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 96d6bb40..614c1b8d 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -23,8 +23,9 @@ class Window(QtQuick.QQuickView): """Main application window""" - def __init__(self): + def __init__(self, app): super(Window, self).__init__(None) + self.app = app self.setTitle(settings.WindowTitle) self.setResizeMode(self.SizeRootObjectToView) @@ -32,17 +33,6 @@ def __init__(self): self.resize(*settings.WindowSize) self.setMinimumSize(QtCore.QSize(430, 300)) - -class NativeVessel(QtGui.QWindow): - """Container window""" - - def __init__(self, app): - super(NativeVessel, self).__init__(None) - self.app = app - - def resizeEvent(self, event): - self.app.resize(self.width(), self.height()) - def event(self, event): """Allow GUI to be closed upon holding Shift""" if event.type() == QtCore.QEvent.Close: @@ -63,6 +53,24 @@ def event(self, event): print("Not ready, hold SHIFT to force an exit") event.ignore() + return super(Window, self).event(event) + + +class NativeVessel(QtGui.QWindow): + """Container window""" + + def __init__(self, app): + super(NativeVessel, self).__init__(None) + self.app = app + + def resizeEvent(self, event): + self.app.resize(self.width(), self.height()) + + def event(self, event): + # Is required for Foster mode + if event.type() == QtCore.QEvent.Close: + self.app.window.event(event) + return super(NativeVessel, self).event(event) @@ -98,7 +106,7 @@ def __init__(self, source, targets=[]): native_vessel = NativeVessel(self) - window = Window() + window = Window(self) window.statusChanged.connect(self.on_status_changed) engine = window.engine() @@ -153,6 +161,10 @@ def register_client(self, port): def deregister_client(self, port): self.clients.pop(port) + def quit(self): + self.controller.host.emit("pyblishQmlClose") + super(Application, self).quit() + @util.SlotSentinel() def show(self, client_settings=None, window_id=None): """Display GUI From e237123ab18f1fb479bbdd41a8ef8c9c10d9a07d Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Fri, 31 Aug 2018 19:35:11 +0800 Subject: [PATCH 08/28] Use pyblish callback to trigger eventFilter's removal --- pyblish_qml/host.py | 47 ++++++++++++++++++++++++++++++++++----- pyblish_qml/ipc/server.py | 23 ++++--------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 34326f54..b45443fd 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -277,6 +277,33 @@ def install_host(use_threaded_wrapper): break +SIGNALS_TO_REMOVE_EVENT_FILTER = ( + "pyblishQmlClose", + "pyblishQmlCloseForced", +) + + +def remove_event_filter(): + event_filter = _state.get("eventFilter") + if isinstance(event_filter, QtCore.QObject): + + # (NOTE) Should remove from the QApp instance which originally + # installed to. + # This will not work: + # `QApplication.instance().removeEventFilter(event_filter)` + # + event_filter.parent().removeEventFilter(event_filter) + del _state["eventFilter"] + + for signal in SIGNALS_TO_REMOVE_EVENT_FILTER: + try: + pyblish.api.deregister_callback(signal, remove_event_filter) + except (KeyError, ValueError): + pass + + print("The eventFilter of pyblish-qml has been removed.\n") + + def install_event_filter(): main_window = _state.get("vesselParent") @@ -298,6 +325,10 @@ def install_event_filter(): print(e) else: _state["eventFilter"] = event_filter + + for signal in SIGNALS_TO_REMOVE_EVENT_FILTER: + pyblish.api.register_callback(signal, remove_event_filter) + print("Event filter has been installed.") @@ -349,14 +380,18 @@ def eventFilter(self, widget, event): # proxy is None, or does not have the function return False - try: - func() - return True - except IOError: + print(func_name) + connected = func() + + if connected is not True: # The running instance has already been closed. - _state.pop("currentServer") + self.parent().removeEventFilter(self) + if _state.get("eventFilter") is self: + _state.pop("eventFilter") + + print("The eventFilter of pyblish-qml has self removed.\n") - return False + return True def _acquire_host_main_window(app): diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index cc798d6f..e85f9762 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -85,7 +85,7 @@ def show(self, settings=None): def hide(self): """Hide the GUI""" self._dispatch("hide") - self.vessel.hide() + return self.vessel.hide() def quit(self): """Ask the GUI to quit""" @@ -94,15 +94,15 @@ def quit(self): def rise(self): """Rise GUI from hidden""" self.vessel.show() - self._dispatch("rise") + return self._dispatch("rise") def inFocus(self): """Set GUI on-top flag""" - self._dispatch("inFocus") + return self._dispatch("inFocus") def outFocus(self): """Remove GUI on-top flag""" - self._dispatch("outFocus") + return self._dispatch("outFocus") def kill(self): """Forcefully destroy the process""" @@ -128,20 +128,6 @@ def publish(self): def validate(self): return self._dispatch("validate") - def _remove_event_filter(self): - event_filter = _state.get("eventFilter") - if isinstance(event_filter, QtCore.QObject): - - # (NOTE) Should remove from the QApp instance which originally - # installed to. - # This will not work: - # `QApplication.instance().removeEventFilter(event_filter)` - # - event_filter.parent().removeEventFilter(event_filter) - del _state["eventFilter"] - - print("The eventFilter of pyblish-qml has been removed.\n") - def _alive(self): """Send pulse to child process @@ -192,7 +178,6 @@ def _flush(self, data): except IOError: # subprocess closed self.vessel.close() - self._remove_event_filter() else: return True From 33794e2f2f5d5179b160df52a887afed98a3f54f Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Fri, 31 Aug 2018 19:39:08 +0800 Subject: [PATCH 09/28] cleanup --- pyblish_qml/host.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index b45443fd..26ed4c71 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -380,7 +380,6 @@ def eventFilter(self, widget, event): # proxy is None, or does not have the function return False - print(func_name) connected = func() if connected is not True: From 8e0ddf0fa2167437ca6a7b1f9ec8143cbdb7c4ff Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Fri, 31 Aug 2018 20:14:28 +0800 Subject: [PATCH 10/28] Install eventFilter before server.listen --- pyblish_qml/host.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 26ed4c71..c5964602 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -175,11 +175,11 @@ def on_shown(): print("Success. QML server available as " "pyblish_qml.api.current_server()") - server.listen() - # Install eventFilter if not exists one install_event_filter() + server.listen() + return server From 8ffec399b187c6d596ad8e4eec5c3b5ccb0220c5 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Fri, 31 Aug 2018 20:43:11 +0800 Subject: [PATCH 11/28] Disable foster mode when modal is on --- pyblish_qml/host.py | 18 +++++++++++------- pyblish_qml/ipc/server.py | 5 +---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index c5964602..1f463496 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -64,7 +64,7 @@ def install(modal, foster): sys.stdout.write("Already installed, uninstalling..\n") uninstall() - use_threaded_wrapper = foster or not modal + use_threaded_wrapper = not modal install_callbacks() install_host(use_threaded_wrapper) @@ -89,17 +89,21 @@ def _is_headless(): ) -def _fosterable(foster=None): +def _fosterable(foster, modal): if foster is None: # Get foster mode from environment foster = bool(os.environ.get("PYBLISH_QML_FOSTER", False)) if foster: - os.environ["PYBLISH_QML_FOSTER"] = "True" - else: - os.environ["PYBLISH_QML_FOSTER"] = "" + if modal or _is_headless(): + print("Foster disabled due to Modal is on or in headless mode.") + return False - return foster and not _is_headless() + print("Foster on.") + return True + + else: + return False def show(parent=None, targets=[], modal=None, foster=None): @@ -116,7 +120,7 @@ def show(parent=None, targets=[], modal=None, foster=None): is_headless = _is_headless() - foster = _fosterable(foster) + foster = _fosterable(foster, modal) # Automatically install if not already installed. if not _state.get("installed"): diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index e85f9762..9eb8f5c1 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -38,8 +38,6 @@ def __init__(self, proxy): self._winId = winIdFixed(self.winId()) self.resize(1, 1) - # Modal Mode - self.setModal(proxy.modal) self.proxy = proxy @@ -62,7 +60,6 @@ class Proxy(object): def __init__(self, server): self.popen = server.popen - self.modal = server.modal self.foster = server.foster self.vessel = FosterVessel(self) if self.foster else MockFosterVessel() @@ -383,7 +380,7 @@ def _listen(): sys.stdout.write(line) if not self.listening: - if self.modal and not self.foster: + if self.modal: _listen() else: thread = threading.Thread(target=_listen) From d210a299a387ac0e10632c9b4b7864f23d1360da Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 02:33:48 +0800 Subject: [PATCH 12/28] Ensure window closeEvent received in foster mode --- pyblish_qml/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 614c1b8d..7ae10774 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -162,7 +162,8 @@ def deregister_client(self, port): self.clients.pop(port) def quit(self): - self.controller.host.emit("pyblishQmlClose") + if self.fostered: + self.window.event(QtCore.QEvent(QtCore.QEvent.Close)) super(Application, self).quit() @util.SlotSentinel() From bc7c0ac63ea2fca5169f5e0360a67926848e3d5f Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 02:39:40 +0800 Subject: [PATCH 13/28] Avoid main thread hanging on window close When foster mode is on, main thread often hanged on signal emission which triggered by window closing (after second reset). By running signal emission call directly instead of running in main thread could avoid the hanging. --- pyblish_qml/ipc/server.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 9eb8f5c1..5e6e9ef2 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -343,6 +343,10 @@ def _listen(): getattr(self.proxy, func_name)(*args) result = None + elif func_name in ("emit",): + # Avoid main thread hang + result = getattr(self.service, func_name)(*args) + else: wrapper = _state.get("dispatchWrapper", default_wrapper) From 3969a0196239457717d554cc3bb6b66358189eb8 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 03:04:36 +0800 Subject: [PATCH 14/28] Detach before running Rest in foster mode --- pyblish_qml/control.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 0d428a96..1b2dc9a3 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -770,6 +770,8 @@ def on_finished(plugins, context): self.host.emit("reset", context=None) + self.attach() + # Hidden sections for section in self.data["models"]["item"].sections: if section.name in settings.HiddenSections: @@ -822,6 +824,7 @@ def on_context(context): def on_reset(): util.async(self.host.context, callback=on_context) + self.detach() util.async(self.host.reset, callback=on_reset) @QtCore.pyqtSlot() From bd3f4b53ba7329f9b6e48231db5620cbabb9246c Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 04:14:08 +0800 Subject: [PATCH 15/28] Cosmetic --- pyblish_qml/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 1f463496..47bde994 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -305,7 +305,7 @@ def remove_event_filter(): except (KeyError, ValueError): pass - print("The eventFilter of pyblish-qml has been removed.\n") + print("The eventFilter of pyblish-qml has been removed.") def install_event_filter(): From 23cae971b66a1a0b076a8a5fcf162f52b2a554e3 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 04:20:53 +0800 Subject: [PATCH 16/28] Aviod complicate detach-attach in foster mode's first run --- pyblish_qml/app.py | 40 ++++++++++++++++++++++----------------- pyblish_qml/control.py | 6 ++++-- pyblish_qml/ipc/server.py | 1 - 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 7ae10774..27b4d196 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -122,7 +122,7 @@ def __init__(self, source, targets=[]): self.fostered = False self.foster_vessel = None - self.vessel = self.native_vessel = native_vessel + self.native_vessel = native_vessel self.window = window self.engine = engine @@ -192,25 +192,25 @@ def show(self, client_settings=None, window_id=None): "This is a bug.".format(window_id)) self.window.setParent(foster_vessel) - - self.vessel = self.foster_vessel = foster_vessel + self.foster_vessel = foster_vessel if client_settings: # Apply client-side settings settings.from_dict(client_settings) - self.vessel.setWidth(client_settings["WindowSize"][0]) - self.vessel.setHeight(client_settings["WindowSize"][1]) - self.vessel.setTitle(client_settings["WindowTitle"]) - self.vessel.setFramePosition( - QtCore.QPoint( - client_settings["WindowPosition"][0], - client_settings["WindowPosition"][1] - ) - ) + def first_appearance_setup(vessel): + vessel.setGeometry(client_settings["WindowPosition"][0], + client_settings["WindowPosition"][1], + client_settings["WindowSize"][0], + client_settings["WindowSize"][1]) + vessel.setTitle(client_settings["WindowTitle"]) + + first_appearance_setup(self.native_vessel) if self.fostered: - self.native_vessel.setTitle(client_settings["WindowTitle"]) + # Return it back to native vessel for first run + self.window.setParent(self.native_vessel) + first_appearance_setup(self.foster_vessel) message = list() message.append("Settings: ") @@ -243,6 +243,14 @@ def show(self, client_settings=None, window_id=None): # Allow time for QML to initialise util.schedule(self.controller.reset, 500, channel="main") + if self.fostered: + # Reclaim window after first rest + self.window.setParent(self.foster_vessel) + self.foster_vessel.show() + # Hide src container + self.native_vessel.setOpacity(0) # avoid hide window anim + self.native_vessel.hide() + def hide(self): """Hide GUI @@ -304,10 +312,9 @@ def detach(self): print("Detach window from foster parent...") - self.vessel = self.native_vessel self.fostered = False - self.window.setParent(self.native_vessel) + # Show dst container self.native_vessel.setOpacity(100) self.native_vessel.show() @@ -340,10 +347,9 @@ def attach(self): print("Attach window to foster parent...") - self.vessel = self.foster_vessel self.fostered = True - self.window.setParent(self.foster_vessel) + # Show dst container (will wait for host) host_attached = QtTest.QSignalSpy(self.host_attached) self.host.attach(self.native_vessel.geometry()) diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 1b2dc9a3..ae07966c 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -770,7 +770,8 @@ def on_finished(plugins, context): self.host.emit("reset", context=None) - self.attach() + if not self.data["firstRun"]: + self.attach() # Hidden sections for section in self.data["models"]["item"].sections: @@ -824,7 +825,8 @@ def on_context(context): def on_reset(): util.async(self.host.context, callback=on_context) - self.detach() + if not self.data["firstRun"]: + self.detach() util.async(self.host.reset, callback=on_reset) @QtCore.pyqtSlot() diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 5e6e9ef2..0aa59c9e 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -76,7 +76,6 @@ def show(self, settings=None): settings (optional, dict): Client settings """ - self.vessel.show() return self._dispatch("show", args=[settings or {}, self._winId]) def hide(self): From 8a5b469340f776930272d471909e1fa7729a9fe4 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 06:57:40 +0800 Subject: [PATCH 17/28] Quit app when force quitting is triggered in foster mode --- pyblish_qml/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 27b4d196..146097b8 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -68,8 +68,12 @@ def resizeEvent(self, event): def event(self, event): # Is required for Foster mode + # Native vessel will receive closeEvent while foster mode is on + # and is the parent of window. if event.type() == QtCore.QEvent.Close: self.app.window.event(event) + if event.isAccepted(): + self.app.quit() return super(NativeVessel, self).event(event) @@ -163,6 +167,9 @@ def deregister_client(self, port): def quit(self): if self.fostered: + # Foster vessel's closeEvent will trigger "quit" which connected + # to here. + # Forward the event to window. self.window.event(QtCore.QEvent(QtCore.QEvent.Close)) super(Application, self).quit() From 5869876684b6a058e93eaad46a8a79049bf47d54 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 17:25:36 +0800 Subject: [PATCH 18/28] Add alert on finished in foster mode --- pyblish_qml/app.py | 8 +++++--- pyblish_qml/control.py | 8 ++++---- pyblish_qml/ipc/client.py | 4 ++-- pyblish_qml/ipc/server.py | 8 ++++++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 146097b8..62187bd6 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -98,7 +98,7 @@ class Application(QtGui.QGuiApplication): inFocused = QtCore.pyqtSignal() outFocused = QtCore.pyqtSignal() - attached = QtCore.pyqtSignal() + attached = QtCore.pyqtSignal(QtCore.QVariant) detached = QtCore.pyqtSignal() host_attached = QtCore.pyqtSignal() host_detached = QtCore.pyqtSignal() @@ -257,6 +257,8 @@ def first_appearance_setup(vessel): # Hide src container self.native_vessel.setOpacity(0) # avoid hide window anim self.native_vessel.hide() + # Ensure at front + self.window.requestActivate() def hide(self): """Hide GUI @@ -336,7 +338,7 @@ def detach(self): self.controller.detached.emit() - def attach(self): + def attach(self, alert=False): """Attach QQuickView window to the host In foster mode, inorder to prevent window freeze when the host's @@ -365,7 +367,7 @@ def attach(self): self.native_vessel.setOpacity(0) # avoid hide window anim self.native_vessel.hide() # Stay on top - self.host.popup() + self.host.popup(alert) self.controller.attached.emit() diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index ae07966c..60feb47d 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -139,8 +139,8 @@ def detach(self): detached = QtTest.QSignalSpy(self.detached) detached.wait(1000) - def attach(self): - signal = json.dumps({"payload": {"name": "attach"}}) + def attach(self, alert=False): + signal = json.dumps({"payload": {"name": "attach", "args": [alert]}}) self.host.channels["parent"].put(signal) attached = QtTest.QSignalSpy(self.attached) attached.wait(1000) @@ -869,7 +869,7 @@ def on_data_received(args): self.run(*args, callback=on_finished) def on_finished(): - self.attach() + self.attach(True) self.host.emit("published", context=None) self.detach() @@ -913,7 +913,7 @@ def on_data_received(args): self.run(*args, callback=on_finished) def on_finished(): - self.attach() + self.attach(True) self.host.emit("validated", context=None) self.detach() diff --git a/pyblish_qml/ipc/client.py b/pyblish_qml/ipc/client.py index 4ec45258..433a1031 100644 --- a/pyblish_qml/ipc/client.py +++ b/pyblish_qml/ipc/client.py @@ -40,8 +40,8 @@ def attach(self, qRect): geometry = [qRect.x(), qRect.y(), qRect.width(), qRect.height()] self._dispatch("attach", args=geometry) - def popup(self): - self._dispatch("popup") + def popup(self, alert): + self._dispatch("popup", args=[alert]) def test(self, **vars): """Vars can only be passed as a non-keyword argument""" diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 0aa59c9e..16a53150 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -115,8 +115,12 @@ def attach(self, x, y, w, h): self.vessel.setGeometry(x, y, w, h) self._dispatch("host_attach") - def popup(self): - self.vessel.activateWindow() # to top + def popup(self, alert): + # No hijack keyboard focus + QtWidgets.QApplication.setActiveWindow(self.vessel) + # Plus alert + if alert: + QtWidgets.QApplication.alert(self.vessel.parent(), 0) def publish(self): return self._dispatch("publish") From e598d2ae0ddf412d1e166c7bdf0c1115f22fa3a1 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 18:01:25 +0800 Subject: [PATCH 19/28] Cosmetic --- pyblish_qml/app.py | 2 +- pyblish_qml/ipc/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 62187bd6..2d7285d3 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -325,9 +325,9 @@ def detach(self): self.window.setParent(self.native_vessel) # Show dst container - self.native_vessel.setOpacity(100) self.native_vessel.show() self.native_vessel.setGeometry(self.foster_vessel.geometry()) + self.native_vessel.setOpacity(100) # Hide src container (will wait for host) host_detached = QtTest.QSignalSpy(self.host_detached) self.host.detach() diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 16a53150..6b984c4a 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -110,9 +110,9 @@ def detach(self): self._dispatch("host_detach") def attach(self, x, y, w, h): - self.vessel.setWindowOpacity(100) self.vessel.show() self.vessel.setGeometry(x, y, w, h) + self.vessel.setWindowOpacity(100) self._dispatch("host_attach") def popup(self, alert): From 5eb3f66e046a3ca67287077b4e15dc9302a830fb Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 1 Sep 2018 22:51:35 +0800 Subject: [PATCH 20/28] Implement foster attach-detach switch When Foster mode is on, QML will not render properly with ninja-like window detach-attach on host with Qt4, this switch will avoid detach-attach in such host. --- pyblish_qml/app.py | 31 +++++++++++++++++--------- pyblish_qml/host.py | 28 ++++++++++++++++++----- pyblish_qml/ipc/server.py | 47 ++++++++++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 2d7285d3..2707acb3 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -67,12 +67,13 @@ def resizeEvent(self, event): self.app.resize(self.width(), self.height()) def event(self, event): - # Is required for Foster mode + # Is required when Foster mode is on. # Native vessel will receive closeEvent while foster mode is on # and is the parent of window. if event.type() == QtCore.QEvent.Close: self.app.window.event(event) if event.isAccepted(): + # `app.fostered` is False at this moment. self.app.quit() return super(NativeVessel, self).event(event) @@ -86,7 +87,7 @@ class Application(QtGui.QGuiApplication): """ - shown = QtCore.pyqtSignal(QtCore.QVariant, QtCore.QVariant) + shown = QtCore.pyqtSignal(*(QtCore.QVariant,) * 3) hidden = QtCore.pyqtSignal() quitted = QtCore.pyqtSignal() published = QtCore.pyqtSignal() @@ -124,6 +125,7 @@ def __init__(self, source, targets=[]): context.setContextProperty("app", controller) self.fostered = False + self.ninja = False self.foster_vessel = None self.native_vessel = native_vessel @@ -166,15 +168,19 @@ def deregister_client(self, port): self.clients.pop(port) def quit(self): + event = None if self.fostered: # Foster vessel's closeEvent will trigger "quit" which connected # to here. # Forward the event to window. - self.window.event(QtCore.QEvent(QtCore.QEvent.Close)) - super(Application, self).quit() + event = QtCore.QEvent(QtCore.QEvent.Close) + self.window.event(event) + + if event is None or event.isAccepted(): + super(Application, self).quit() @util.SlotSentinel() - def show(self, client_settings=None, window_id=None): + def show(self, client_settings=None, window_id=None, ninja=False): """Display GUI Once the QML interface has been loaded, use this @@ -200,6 +206,7 @@ def show(self, client_settings=None, window_id=None): self.window.setParent(foster_vessel) self.foster_vessel = foster_vessel + self.ninja = ninja if client_settings: # Apply client-side settings @@ -215,8 +222,9 @@ def first_appearance_setup(vessel): first_appearance_setup(self.native_vessel) if self.fostered: - # Return it back to native vessel for first run - self.window.setParent(self.native_vessel) + if self.ninja: + # Return it back to native vessel for first run + self.window.setParent(self.native_vessel) first_appearance_setup(self.foster_vessel) message = list() @@ -250,7 +258,7 @@ def first_appearance_setup(vessel): # Allow time for QML to initialise util.schedule(self.controller.reset, 500, channel="main") - if self.fostered: + if self.fostered and self.ninja: # Reclaim window after first rest self.window.setParent(self.foster_vessel) self.foster_vessel.show() @@ -315,7 +323,7 @@ def detach(self): This is the part that detaching from host. """ - if self.foster_vessel is None: + if not self.ninja: self.controller.detached.emit() return @@ -350,7 +358,10 @@ def attach(self, alert=False): This is the part that attaching back to host. """ - if self.foster_vessel is None: + if not self.ninja: + if self.foster_vessel is not None: + # Send alert + self.host.popup(alert) self.controller.attached.emit() return diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 47bde994..5259714a 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -106,6 +106,24 @@ def _fosterable(foster, modal): return False +def _foster_ninja(foster): + if not foster: + return False + + value = os.environ.get("PYBLISH_QML_FOSTER_NINJA", "").lower() + if value in ("true", "yes", "1"): + return True + + elif value in ("false", "no", "0"): + return False + + else: + if QtCore.qVersion()[0] == "5": + return True + else: + return False + + def show(parent=None, targets=[], modal=None, foster=None): """Attempt to show GUI @@ -121,6 +139,7 @@ def show(parent=None, targets=[], modal=None, foster=None): is_headless = _is_headless() foster = _fosterable(foster, modal) + ninja = _foster_ninja(foster) # Automatically install if not already installed. if not _state.get("installed"): @@ -164,7 +183,8 @@ def on_shown(): server = ipc.server.Server(service, targets=targets, modal=modal, - foster=foster) + foster=foster, + ninja=ninja) except Exception: # If for some reason, the GUI fails to show. traceback.print_exc() @@ -388,11 +408,7 @@ def eventFilter(self, widget, event): if connected is not True: # The running instance has already been closed. - self.parent().removeEventFilter(self) - if _state.get("eventFilter") is self: - _state.pop("eventFilter") - - print("The eventFilter of pyblish-qml has self removed.\n") + remove_event_filter() return True diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 6b984c4a..6c449809 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -15,6 +15,12 @@ IS_WIN32 = sys.platform == "win32" +SIGNALS_TO_CLOSE_VESSEL = ( + "pyblishQmlClose", + "pyblishQmlCloseForced", +) + + def default_wrapper(func, *args, **kwargs): return func(*args, **kwargs) @@ -40,9 +46,14 @@ def __init__(self, proxy): self.resize(1, 1) self.proxy = proxy + self.close_lock = True def closeEvent(self, event): - self.proxy.quit() + if self.close_lock: + self.proxy.quit() + event.ignore() + else: + event.accept() def resizeEvent(self, event): self.proxy._dispatch("resize", args=[self.width(), self.height()]) @@ -50,7 +61,7 @@ def resizeEvent(self, event): class MockFosterVessel(object): """We don't create widget without QApp, we mock one""" - _winId = None + _winId = close_lock = None show = hide = close = lambda _: None @@ -58,15 +69,32 @@ class Proxy(object): """Speak to child process and control the vessel (window container)""" def __init__(self, server): + import pyblish.api self.popen = server.popen self.foster = server.foster + self.ninja = server.ninja self.vessel = FosterVessel(self) if self.foster else MockFosterVessel() self._winId = self.vessel._winId server.proxy = self + def close_vessel(): + self.vessel.close_lock = False + self.vessel.close() + + for signal in SIGNALS_TO_CLOSE_VESSEL: + try: + pyblish.api.deregister_callback(signal, self.close_vessel) + except (KeyError, ValueError): + pass + + self.close_vessel = close_vessel + + for signal in SIGNALS_TO_CLOSE_VESSEL: + pyblish.api.register_callback(signal, self.close_vessel) + self._alive() def show(self, settings=None): @@ -76,7 +104,11 @@ def show(self, settings=None): settings (optional, dict): Client settings """ - return self._dispatch("show", args=[settings or {}, self._winId]) + if not self.ninja: + self.vessel.show() + return self._dispatch("show", args=[settings or {}, + self._winId, + self.ninja]) def hide(self): """Hide the GUI""" @@ -117,7 +149,8 @@ def attach(self, x, y, w, h): def popup(self, alert): # No hijack keyboard focus - QtWidgets.QApplication.setActiveWindow(self.vessel) + if self.ninja: + QtWidgets.QApplication.setActiveWindow(self.vessel) # Plus alert if alert: QtWidgets.QApplication.alert(self.vessel.parent(), 0) @@ -177,7 +210,7 @@ def _flush(self, data): self.popen.stdin.flush() except IOError: # subprocess closed - self.vessel.close() + self.close_vessel() else: return True @@ -200,7 +233,8 @@ def __init__(self, pyqt5=None, targets=[], modal=False, - foster=False): + foster=False, + ninja=False): super(Server, self).__init__() self.service = service self.listening = False @@ -211,6 +245,7 @@ def __init__(self, self.modal = modal self.foster = foster + self.ninja = ninja # The server may be run within Maya or some other host, # in which case we refer to it as running embedded. From 2a6956894e97ee73a2a9cc232d38b7cc48011d11 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sun, 2 Sep 2018 02:20:27 +0800 Subject: [PATCH 21/28] Remove OS restriction --- pyblish_qml/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 2707acb3..57de3b8b 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -283,14 +283,14 @@ def rise(self): def inFocus(self): """Set GUI on-top flag""" - if not self.fostered and os.name == "nt": + if not self.fostered: previous_flags = self.window.flags() self.window.setFlags(previous_flags | QtCore.Qt.WindowStaysOnTopHint) def outFocus(self): """Remove GUI on-top flag""" - if not self.fostered and os.name == "nt": + if not self.fostered: previous_flags = self.window.flags() self.window.setFlags(previous_flags ^ QtCore.Qt.WindowStaysOnTopHint) @@ -303,7 +303,7 @@ def resize(self, width, height): self.window.resize(width, height) def _popup(self): - if not self.fostered and os.name == "nt": + if not self.fostered: window = self.window # Work-around for window appearing behind # other windows upon being shown once hidden. From 6ea9b6db3837846f5214a59b1952970b1a124568 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sun, 2 Sep 2018 03:08:01 +0800 Subject: [PATCH 22/28] Foster mode first run in native vessel --- pyblish_qml/app.py | 13 +++---------- pyblish_qml/control.py | 3 +-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 57de3b8b..69df13a9 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -234,6 +234,9 @@ def first_appearance_setup(vessel): print("\n".join(message)) + if self.fostered and self.ninja: + self.native_vessel.show() + self.window.requestActivate() self.window.showNormal() @@ -258,16 +261,6 @@ def first_appearance_setup(vessel): # Allow time for QML to initialise util.schedule(self.controller.reset, 500, channel="main") - if self.fostered and self.ninja: - # Reclaim window after first rest - self.window.setParent(self.foster_vessel) - self.foster_vessel.show() - # Hide src container - self.native_vessel.setOpacity(0) # avoid hide window anim - self.native_vessel.hide() - # Ensure at front - self.window.requestActivate() - def hide(self): """Hide GUI diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 60feb47d..b989ad3f 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -770,8 +770,7 @@ def on_finished(plugins, context): self.host.emit("reset", context=None) - if not self.data["firstRun"]: - self.attach() + self.attach() # Hidden sections for section in self.data["models"]["item"].sections: From 7b3277cc9a28b4dbd1daaafebc28bdca1a524ffd Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 2 Sep 2018 10:07:08 +0100 Subject: [PATCH 23/28] Cosmetics --- pyblish_qml/host.py | 18 +++++++----------- pyblish_qml/ipc/server.py | 13 +++++++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 5259714a..0b253c54 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -111,17 +111,7 @@ def _foster_ninja(foster): return False value = os.environ.get("PYBLISH_QML_FOSTER_NINJA", "").lower() - if value in ("true", "yes", "1"): - return True - - elif value in ("false", "no", "0"): - return False - - else: - if QtCore.qVersion()[0] == "5": - return True - else: - return False + return value in ("true", "yes", "1") or QtCore.qVersion()[0] == "5" def show(parent=None, targets=[], modal=None, foster=None): @@ -130,6 +120,12 @@ def show(parent=None, targets=[], modal=None, foster=None): Requires install() to have been run first, and a live instance of Pyblish QML in the background. + Arguments: + parent (None, optional): Deprecated + targets (list, optional): Publishing targets + modal (bool, optional): Block interactions to parent + foster (bool, optional): Become a real child of the parent process + """ # Get modal mode from environment diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 6c449809..cc67329a 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -104,11 +104,12 @@ def show(self, settings=None): settings (optional, dict): Client settings """ - if not self.ninja: + if self.ninja: + return self._dispatch("show", args=[settings or {}, + self._winId, + self.ninja]) + else: self.vessel.show() - return self._dispatch("show", args=[settings or {}, - self._winId, - self.ninja]) def hide(self): """Hide the GUI""" @@ -224,6 +225,10 @@ class Server(object): service (service.Service): Dispatch requests to this service python (str, optional): Absolute path to Python executable pyqt5 (str, optional): Absolute path to PyQt5 + targets (list, optional): Publishing targets, e.g. `ftrack` + modal (bool, optional): Block interactions to parent + foster (bool, optional): Become a real child of the parent process + ninja (bool, optional): ...? """ From 64e24eb18987a1a7b5a7ad413503238d84bfa177 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 2 Sep 2018 10:07:36 +0100 Subject: [PATCH 24/28] Document foster and modal environment variables --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 094f6a32..59d50506 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,11 @@ api.register_gui("pyblish_qml") See [pyblish-maya](https://github.com/pyblish/pyblish-maya#usage) for an example. +**Additional Environment Variables** + +- `PYBLISH_QML_FOSTER=1` Make QML process a real child of parent process, this makes the otherwise external process act like a native window within a host, to appear below inner windows such as the Script Editor in Maya. +- `PYBLISH_QML_MODAL=1` Block interactions to parent process, useful for headless publishing where you expect a process to remain alive for as long as QML is. Without this, Pyblish is at the mercy of the parent process, e.g. `mayapy` which quits at the first sign of EOF. +


From 3dca54593d49494901c794a7ee7f817e840a9dc2 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 2 Sep 2018 10:23:39 +0100 Subject: [PATCH 25/28] Remove app argument from Window --- pyblish_qml/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 69df13a9..54cecb95 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -23,9 +23,9 @@ class Window(QtQuick.QQuickView): """Main application window""" - def __init__(self, app): + def __init__(self): super(Window, self).__init__(None) - self.app = app + self.app = QtGui.QGuiApplication.instance() self.setTitle(settings.WindowTitle) self.setResizeMode(self.SizeRootObjectToView) @@ -59,9 +59,9 @@ def event(self, event): class NativeVessel(QtGui.QWindow): """Container window""" - def __init__(self, app): + def __init__(self): super(NativeVessel, self).__init__(None) - self.app = app + self.app = QtGui.QGuiApplication.instance() def resizeEvent(self, event): self.app.resize(self.width(), self.height()) @@ -109,9 +109,9 @@ def __init__(self, source, targets=[]): self.setWindowIcon(QtGui.QIcon(ICON_PATH)) - native_vessel = NativeVessel(self) + native_vessel = NativeVessel() - window = Window(self) + window = Window() window.statusChanged.connect(self.on_status_changed) engine = window.engine() From 4d4f8a21aacdc85d57fa4a889304a6f183d740dc Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Sep 2018 01:39:20 +0800 Subject: [PATCH 26/28] Restore `server.Proxy.show` from 7b3277c --- pyblish_qml/ipc/server.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index cc67329a..9975544f 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -104,12 +104,11 @@ def show(self, settings=None): settings (optional, dict): Client settings """ - if self.ninja: - return self._dispatch("show", args=[settings or {}, - self._winId, - self.ninja]) - else: + if not self.ninja: self.vessel.show() + return self._dispatch("show", args=[settings or {}, + self._winId, + self.ninja]) def hide(self): """Hide the GUI""" From 4b9b6a50f4889e36b2a3b3bd26072077b2f1b50e Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Sep 2018 02:09:23 +0800 Subject: [PATCH 27/28] Rename uncleared param `ninja` to `foster_fixed` --- pyblish_qml/app.py | 19 +++++++++---------- pyblish_qml/host.py | 10 +++++----- pyblish_qml/ipc/server.py | 16 ++++++++-------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 54cecb95..76061559 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -125,7 +125,7 @@ def __init__(self, source, targets=[]): context.setContextProperty("app", controller) self.fostered = False - self.ninja = False + self.foster_fixed = False self.foster_vessel = None self.native_vessel = native_vessel @@ -180,7 +180,7 @@ def quit(self): super(Application, self).quit() @util.SlotSentinel() - def show(self, client_settings=None, window_id=None, ninja=False): + def show(self, client_settings=None, window_id=None, foster_fixed=False): """Display GUI Once the QML interface has been loaded, use this @@ -206,7 +206,7 @@ def show(self, client_settings=None, window_id=None, ninja=False): self.window.setParent(foster_vessel) self.foster_vessel = foster_vessel - self.ninja = ninja + self.foster_fixed = foster_fixed if client_settings: # Apply client-side settings @@ -222,7 +222,7 @@ def first_appearance_setup(vessel): first_appearance_setup(self.native_vessel) if self.fostered: - if self.ninja: + if not self.foster_fixed: # Return it back to native vessel for first run self.window.setParent(self.native_vessel) first_appearance_setup(self.foster_vessel) @@ -234,7 +234,7 @@ def first_appearance_setup(vessel): print("\n".join(message)) - if self.fostered and self.ninja: + if self.fostered and not self.foster_fixed: self.native_vessel.show() self.window.requestActivate() @@ -316,7 +316,7 @@ def detach(self): This is the part that detaching from host. """ - if not self.ninja: + if self.foster_fixed or self.foster_vessel is None: self.controller.detached.emit() return @@ -351,11 +351,10 @@ def attach(self, alert=False): This is the part that attaching back to host. """ - if not self.ninja: - if self.foster_vessel is not None: - # Send alert - self.host.popup(alert) + if self.foster_fixed or self.foster_vessel is None: self.controller.attached.emit() + if self.foster_vessel is not None: + self.host.popup(alert) # Send alert return print("Attach window to foster parent...") diff --git a/pyblish_qml/host.py b/pyblish_qml/host.py index 0b253c54..7ec4e466 100644 --- a/pyblish_qml/host.py +++ b/pyblish_qml/host.py @@ -106,12 +106,12 @@ def _fosterable(foster, modal): return False -def _foster_ninja(foster): +def _foster_fixed(foster): if not foster: return False - value = os.environ.get("PYBLISH_QML_FOSTER_NINJA", "").lower() - return value in ("true", "yes", "1") or QtCore.qVersion()[0] == "5" + value = os.environ.get("PYBLISH_QML_FOSTER_FIXED", "").lower() + return value in ("true", "yes", "1") or QtCore.qVersion()[0] == "4" def show(parent=None, targets=[], modal=None, foster=None): @@ -135,7 +135,7 @@ def show(parent=None, targets=[], modal=None, foster=None): is_headless = _is_headless() foster = _fosterable(foster, modal) - ninja = _foster_ninja(foster) + foster_fixed = _foster_fixed(foster) # Automatically install if not already installed. if not _state.get("installed"): @@ -180,7 +180,7 @@ def on_shown(): targets=targets, modal=modal, foster=foster, - ninja=ninja) + foster_fixed=foster_fixed) except Exception: # If for some reason, the GUI fails to show. traceback.print_exc() diff --git a/pyblish_qml/ipc/server.py b/pyblish_qml/ipc/server.py index 9975544f..a8c1ed36 100644 --- a/pyblish_qml/ipc/server.py +++ b/pyblish_qml/ipc/server.py @@ -73,7 +73,7 @@ def __init__(self, server): self.popen = server.popen self.foster = server.foster - self.ninja = server.ninja + self.foster_fixed = server.foster_fixed self.vessel = FosterVessel(self) if self.foster else MockFosterVessel() self._winId = self.vessel._winId @@ -104,11 +104,11 @@ def show(self, settings=None): settings (optional, dict): Client settings """ - if not self.ninja: + if self.foster_fixed: self.vessel.show() return self._dispatch("show", args=[settings or {}, self._winId, - self.ninja]) + self.foster_fixed]) def hide(self): """Hide the GUI""" @@ -149,7 +149,7 @@ def attach(self, x, y, w, h): def popup(self, alert): # No hijack keyboard focus - if self.ninja: + if not self.foster_fixed: QtWidgets.QApplication.setActiveWindow(self.vessel) # Plus alert if alert: @@ -226,8 +226,8 @@ class Server(object): pyqt5 (str, optional): Absolute path to PyQt5 targets (list, optional): Publishing targets, e.g. `ftrack` modal (bool, optional): Block interactions to parent - foster (bool, optional): Become a real child of the parent process - ninja (bool, optional): ...? + foster (bool, optional): GUI become a real child of the parent process + foster_fixed (bool, optional): GUI always remain inside the parent """ @@ -238,7 +238,7 @@ def __init__(self, targets=[], modal=False, foster=False, - ninja=False): + foster_fixed=False): super(Server, self).__init__() self.service = service self.listening = False @@ -249,7 +249,7 @@ def __init__(self, self.modal = modal self.foster = foster - self.ninja = ninja + self.foster_fixed = foster_fixed # The server may be run within Maya or some other host, # in which case we refer to it as running embedded. From 3a0510ed53a765e55ea47f02943316c710d5536e Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Sat, 8 Sep 2018 17:23:33 +0800 Subject: [PATCH 28/28] Version bump --- pyblish_qml/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyblish_qml/version.py b/pyblish_qml/version.py index de3a9db3..082e6304 100644 --- a/pyblish_qml/version.py +++ b/pyblish_qml/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 1 VERSION_MINOR = 8 -VERSION_PATCH = 1 +VERSION_PATCH = 2 version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) version = '%i.%i.%i' % version_info