Skip to content

Commit

Permalink
Merge pull request #312 from davidlatwe/drop-foster
Browse files Browse the repository at this point in the history
Foster Mode Deprecated
  • Loading branch information
davidlatwe authored Jan 6, 2019
2 parents 09b6670 + aaede77 commit b7555f9
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 499 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ 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.

<br>
Expand Down
5 changes: 4 additions & 1 deletion pyblish_qml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
def show(parent=None, targets=None, modal=None, foster=None):
from . import host

if foster is not None:
print("Foster Mode has been deprecated.")

if targets is None:
targets = []
return host.show(parent, targets, modal, foster)
return host.show(parent, targets, modal)


_state = {}
Expand Down
228 changes: 29 additions & 199 deletions pyblish_qml/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
class Window(QtQuick.QQuickView):
"""Main application window"""

def __init__(self):
def __init__(self, parent=None):
super(Window, self).__init__(None)
self.app = QtGui.QGuiApplication.instance()
self.app = parent

self.setTitle(settings.WindowTitle)
self.setResizeMode(self.SizeRootObjectToView)
Expand Down Expand Up @@ -56,29 +56,6 @@ def event(self, event):
return super(Window, self).event(event)


class NativeVessel(QtGui.QWindow):
"""Container window"""

def __init__(self):
super(NativeVessel, self).__init__(None)
self.app = QtGui.QGuiApplication.instance()

def resizeEvent(self, event):
self.app.resize(self.width(), self.height())

def event(self, event):
# 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)


class Application(QtGui.QGuiApplication):
"""Pyblish QML wrapper around QGuiApplication
Expand All @@ -87,31 +64,22 @@ class Application(QtGui.QGuiApplication):
"""

shown = QtCore.pyqtSignal(*(QtCore.QVariant,) * 3)
shown = QtCore.pyqtSignal(QtCore.QVariant)
hidden = QtCore.pyqtSignal()
quitted = QtCore.pyqtSignal()
published = QtCore.pyqtSignal()
validated = QtCore.pyqtSignal()

resized = QtCore.pyqtSignal(QtCore.QVariant, QtCore.QVariant)

risen = QtCore.pyqtSignal()
inFocused = QtCore.pyqtSignal()
outFocused = QtCore.pyqtSignal()

attached = QtCore.pyqtSignal(QtCore.QVariant)
detached = QtCore.pyqtSignal()
host_attached = QtCore.pyqtSignal()
host_detached = QtCore.pyqtSignal()

def __init__(self, source, targets=[]):
super(Application, self).__init__(sys.argv)

self.setWindowIcon(QtGui.QIcon(ICON_PATH))

native_vessel = NativeVessel()

window = Window()
window = Window(self)
window.statusChanged.connect(self.on_status_changed)

engine = window.engine()
Expand All @@ -124,12 +92,6 @@ def __init__(self, source, targets=[]):
context = engine.rootContext()
context.setContextProperty("app", controller)

self.fostered = False
self.foster_fixed = False

self.foster_vessel = None
self.native_vessel = native_vessel

self.window = window
self.engine = engine
self.controller = controller
Expand All @@ -143,15 +105,10 @@ def __init__(self, source, targets=[]):
self.published.connect(self.publish)
self.validated.connect(self.validate)

self.resized.connect(self.resize)

self.risen.connect(self.rise)
self.inFocused.connect(self.inFocus)
self.outFocused.connect(self.outFocus)

self.attached.connect(self.attach)
self.detached.connect(self.detach)

window.setSource(QtCore.QUrl.fromLocalFile(source))

def on_status_changed(self, status):
Expand All @@ -167,20 +124,8 @@ def register_client(self, port):
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.
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, foster_fixed=False):
def show(self, client_settings=None):
"""Display GUI
Once the QML interface has been loaded, use this
Expand All @@ -191,41 +136,20 @@ def show(self, client_settings=None, window_id=None, foster_fixed=False):
client_settings (dict, optional): Visual settings, see settings.py
"""
self.fostered = window_id is not None

if self.fostered:
print("Moving to container window ...")

# Creates a local representation of a window created by another
# process (Maya or other host).
foster_vessel = QtGui.QWindow.fromWinId(window_id)

if foster_vessel is None:
raise RuntimeError("Container window not found, ID: {}\n."
"This is a bug.".format(window_id))

self.window.setParent(foster_vessel)
self.foster_vessel = foster_vessel
self.foster_fixed = foster_fixed
window = self.window

if client_settings:
# Apply client-side settings
settings.from_dict(client_settings)

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:
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)
window.setWidth(client_settings["WindowSize"][0])
window.setHeight(client_settings["WindowSize"][1])
window.setTitle(client_settings["WindowTitle"])
window.setFramePosition(
QtCore.QPoint(
client_settings["WindowPosition"][0],
client_settings["WindowPosition"][1]
)
)

message = list()
message.append("Settings: ")
Expand All @@ -234,13 +158,14 @@ def first_appearance_setup(vessel):

print("\n".join(message))

if self.fostered and not self.foster_fixed:
self.native_vessel.show()

self.window.requestActivate()
self.window.showNormal()
window.requestActivate()
window.showNormal()

self._popup()
# Work-around for window appearing behind
# other windows upon being shown once hidden.
previous_flags = window.flags()
window.setFlags(previous_flags | QtCore.Qt.WindowStaysOnTopHint)
window.setFlags(previous_flags)

# Give statemachine enough time to boot up
if not any(state in self.controller.states
Expand All @@ -259,7 +184,7 @@ def first_appearance_setup(vessel):
self.controller.show.emit()

# Allow time for QML to initialise
util.schedule(self.controller.reset, 1500, channel="main")
util.schedule(self.controller.reset, 500, channel="main")

def hide(self):
"""Hide GUI
Expand All @@ -276,103 +201,15 @@ def rise(self):

def inFocus(self):
"""Set GUI on-top flag"""
if not self.fostered:
previous_flags = self.window.flags()
self.window.setFlags(previous_flags |
QtCore.Qt.WindowStaysOnTopHint)
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:
previous_flags = self.window.flags()
self.window.setFlags(previous_flags ^
QtCore.Qt.WindowStaysOnTopHint)

def resize(self, width, height):
"""Resize GUI with it's vessel (container window)
"""
# (NOTE) Could not get it auto resize with container, this is a
# alternative
self.window.resize(width, height)

def _popup(self):
if not self.fostered:
window = self.window
# Work-around for window appearing behind
# other windows upon being shown once hidden.
previous_flags = window.flags()
window.setFlags(previous_flags | QtCore.Qt.WindowStaysOnTopHint)
window.setFlags(previous_flags)

def detach(self):
"""Detach QQuickView window from the host
In foster mode, inorder to prevent window freeze when the host's
main thread is busy, will detach the QML window from the container
inside the host, and re-parent to the container which spawned by
the subprocess. And attach it back to host when the heavy lifting
is done.
This is the part that detaching from host.
"""
if self.foster_fixed or self.foster_vessel is None:
self.controller.detached.emit()
return

print("Detach window from foster parent...")

self.fostered = False
self.window.setParent(self.native_vessel)

# Show dst container
self.native_vessel.show()
self.native_vessel.setGeometry(self.foster_vessel.geometry())
self.native_vessel.setOpacity(1)
# 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()

self.controller.detached.emit()

def attach(self, alert=False):
"""Attach QQuickView window to the host
In foster mode, inorder to prevent window freeze when the host's
main thread is busy, will detach the QML window from the container
inside the host, and re-parent to the container which spawned by
the subprocess. And attach it back to host when the heavy lifting
is done.
This is the part that attaching back to host.
"""
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...")

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())
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(alert)

self.controller.attached.emit()
previous_flags = self.window.flags()
self.window.setFlags(previous_flags ^
QtCore.Qt.WindowStaysOnTopHint)

def publish(self):
"""Fire up the publish sequence"""
Expand Down Expand Up @@ -408,17 +245,10 @@ def _listen():
"publish": "published",
"validate": "validated",

"resize": "resized",

"rise": "risen",
"inFocus": "inFocused",
"outFocus": "outFocused",

"attach": "attached",
"detach": "detached",
"host_attach": "host_attached",
"host_detach": "host_detached",

}.get(payload["name"])

if not signal:
Expand Down
Loading

0 comments on commit b7555f9

Please sign in to comment.