Skip to content

Commit

Permalink
Merge pull request #297 from davidlatwe/master
Browse files Browse the repository at this point in the history
Implemented eventFilter removal and improved Foster stability
  • Loading branch information
mottosso authored Sep 8, 2018
2 parents 18843b9 + 3a0510e commit 5d5fc0c
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 108 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<br>
<br>
<br>
Expand Down
159 changes: 98 additions & 61 deletions pyblish_qml/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,14 @@ class Window(QtQuick.QQuickView):

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

self.setTitle(settings.WindowTitle)
self.setResizeMode(self.SizeRootObjectToView)

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:
Expand All @@ -63,6 +53,29 @@ 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):
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)


Expand All @@ -74,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()
Expand All @@ -86,15 +99,17 @@ 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()

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

self.setWindowIcon(QtGui.QIcon(ICON_PATH))

native_vessel = NativeVessel(self)
native_vessel = NativeVessel()

window = Window()
window.statusChanged.connect(self.on_status_changed)
Expand All @@ -110,9 +125,10 @@ def __init__(self, source, targets=[]):
context.setContextProperty("app", controller)

self.fostered = False
self.foster_fixed = False

self.foster_vessel = None
self.vessel = self.native_vessel = native_vessel
self.native_vessel = native_vessel

self.window = window
self.engine = engine
Expand Down Expand Up @@ -151,8 +167,20 @@ 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):
def show(self, client_settings=None, window_id=None, foster_fixed=False):
"""Display GUI
Once the QML interface has been loaded, use this
Expand All @@ -177,22 +205,27 @@ 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
self.foster_fixed = foster_fixed

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:
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)

message = list()
message.append("Settings: ")
Expand All @@ -201,6 +234,9 @@ def show(self, client_settings=None, window_id=None):

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

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

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

Expand Down Expand Up @@ -232,22 +268,22 @@ 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"""
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)
Expand All @@ -260,24 +296,14 @@ 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.
previous_flags = window.flags()
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
Expand All @@ -290,26 +316,30 @@ def detach(self):
This is the part that detaching from host.
"""
if self.foster_vessel is None:
if self.foster_fixed or self.foster_vessel is None:
self.controller.detached.emit()
return

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())
self.native_vessel.setOpacity(100)
# 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):
def attach(self, alert=False):
"""Attach QQuickView window to the host
In foster mode, inorder to prevent window freeze when the host's
Expand All @@ -321,21 +351,26 @@ def attach(self):
This is the part that attaching back to host.
"""
if self.foster_vessel is None:
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.vessel = self.foster_vessel

self.host.attach()

self.native_vessel.hide()
self.fostered = True

self.window.setParent(self.vessel)
self._set_goemetry(self.native_vessel, self.vessel)
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()

Expand Down Expand Up @@ -381,6 +416,8 @@ def _listen():

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

}.get(payload["name"])

Expand Down
12 changes: 8 additions & 4 deletions pyblish_qml/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -822,6 +824,8 @@ def on_context(context):
def on_reset():
util.async(self.host.context, callback=on_context)

if not self.data["firstRun"]:
self.detach()
util.async(self.host.reset, callback=on_reset)

@QtCore.pyqtSlot()
Expand Down Expand Up @@ -864,7 +868,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()
Expand Down Expand Up @@ -908,7 +912,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()
Expand Down
Loading

0 comments on commit 5d5fc0c

Please sign in to comment.