From cfb3de859fe0c696884519fd3e5489dd99f4e09e Mon Sep 17 00:00:00 2001 From: David Lai Date: Sat, 1 Feb 2020 05:05:48 +0800 Subject: [PATCH 01/13] Rename attributes to adopt Qt5.py --- pyblish_qml/app.py | 20 ++++----- pyblish_qml/control.py | 94 +++++++++++++++++++++--------------------- pyblish_qml/models.py | 36 ++++++++-------- pyblish_qml/util.py | 14 +++---- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 3de9cb8c..599a5e30 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -9,7 +9,7 @@ import threading # Dependencies -from PyQt5 import QtCore, QtGui, QtQuick, QtTest +from Qt5 import QtCore, QtGui, QtQuick # Local libraries from . import util, compat, control, settings, ipc @@ -64,17 +64,17 @@ class Application(QtGui.QGuiApplication): """ - shown = QtCore.pyqtSignal(QtCore.QVariant) - hidden = QtCore.pyqtSignal() - quitted = QtCore.pyqtSignal() - published = QtCore.pyqtSignal() - validated = QtCore.pyqtSignal() + shown = QtCore.Signal(QtCore.QVariant) + hidden = QtCore.Signal() + quitted = QtCore.Signal() + published = QtCore.Signal() + validated = QtCore.Signal() - targeted = QtCore.pyqtSignal(QtCore.QVariant) + targeted = QtCore.Signal(QtCore.QVariant) - risen = QtCore.pyqtSignal() - inFocused = QtCore.pyqtSignal() - outFocused = QtCore.pyqtSignal() + risen = QtCore.Signal() + inFocused = QtCore.Signal() + outFocused = QtCore.Signal() def __init__(self, source, targets=[]): super(Application, self).__init__(sys.argv) diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 478e5a69..65ea5bbd 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -4,52 +4,52 @@ import collections # Dependencies -from PyQt5 import QtCore +from Qt5 import QtCore import pyblish.logic # Local libraries from . import util, models, version, settings -qtproperty = util.pyqtConstantProperty +qtproperty = util.qtConstantProperty class Controller(QtCore.QObject): """Communicate with QML""" - # PyQt Signals - info = QtCore.pyqtSignal(str, arguments=["message"]) - error = QtCore.pyqtSignal(str, arguments=["message"]) + # Signals + info = QtCore.Signal(str, arguments=["message"]) + error = QtCore.Signal(str, arguments=["message"]) - show = QtCore.pyqtSignal() - hide = QtCore.pyqtSignal() + show = QtCore.Signal() + hide = QtCore.Signal() - firstRun = QtCore.pyqtSignal() + firstRun = QtCore.Signal() - collecting = QtCore.pyqtSignal() - validating = QtCore.pyqtSignal() - extracting = QtCore.pyqtSignal() - integrating = QtCore.pyqtSignal() + collecting = QtCore.Signal() + validating = QtCore.Signal() + extracting = QtCore.Signal() + integrating = QtCore.Signal() - repairing = QtCore.pyqtSignal() - stopping = QtCore.pyqtSignal() - saving = QtCore.pyqtSignal() - initialising = QtCore.pyqtSignal() - acting = QtCore.pyqtSignal() - acted = QtCore.pyqtSignal() + repairing = QtCore.Signal() + stopping = QtCore.Signal() + saving = QtCore.Signal() + initialising = QtCore.Signal() + acting = QtCore.Signal() + acted = QtCore.Signal() # A plug-in/instance pair is about to be processed - about_to_process = QtCore.pyqtSignal(object, object) + about_to_process = QtCore.Signal(object, object) - changed = QtCore.pyqtSignal() + changed = QtCore.Signal() - ready = QtCore.pyqtSignal() - saved = QtCore.pyqtSignal() - finished = QtCore.pyqtSignal() - initialised = QtCore.pyqtSignal() - commented = QtCore.pyqtSignal() - commenting = QtCore.pyqtSignal(str, arguments=["comment"]) + ready = QtCore.Signal() + saved = QtCore.Signal() + finished = QtCore.Signal() + initialised = QtCore.Signal() + commented = QtCore.Signal() + commenting = QtCore.Signal(str, arguments=["comment"]) - state_changed = QtCore.pyqtSignal(str, arguments=["state"]) + state_changed = QtCore.Signal(str, arguments=["state"]) # Statically expose these members to the QML run-time. itemModel = qtproperty(lambda self: self.data["models"]["item"]) @@ -291,20 +291,20 @@ def setup_statemachine(self): machine.start() return machine - @QtCore.pyqtSlot(result=str) + @QtCore.Slot(result=str) def comment(self): """Return first line of comment""" return self.data["comment"] - @QtCore.pyqtProperty(str, notify=state_changed) + @QtCore.Property(str, notify=state_changed) def state(self): return self.data["state"]["current"] - @QtCore.pyqtProperty(bool, notify=commented) + @QtCore.Property(bool, notify=commented) def hasComment(self): return True if self.data["comment"] else False - @QtCore.pyqtProperty(bool, constant=True) + @QtCore.Property(bool, constant=True) def commentEnabled(self): return "comment" in self.host.cached_context.data @@ -312,7 +312,7 @@ def commentEnabled(self): def states(self): return self.data["state"]["all"] - @QtCore.pyqtSlot(result=float) + @QtCore.Slot(result=float) def time(self): return time.time() @@ -377,7 +377,7 @@ def iterator(self, plugins, context): yield result - @QtCore.pyqtSlot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result=QtCore.QVariant) def getPluginActions(self, index): """Return actions from plug-in at `index` @@ -439,7 +439,7 @@ def getPluginActions(self, index): return remaining_actions - @QtCore.pyqtSlot(str) + @QtCore.Slot(str) def runPluginAction(self, action): if "acting" in self.states: return self.error.emit("Busy") @@ -491,7 +491,7 @@ def on_finished(result): util.defer(run, callback=on_finished) - @QtCore.pyqtSlot(int) + @QtCore.Slot(int) def toggleInstance(self, index): models = self.data["models"] proxies = self.data["proxies"] @@ -506,7 +506,7 @@ def toggleInstance(self, index): else: self.error.emit("Cannot toggle") - @QtCore.pyqtSlot(bool, str) + @QtCore.Slot(bool, str) def toggleSection(self, checkState, sectionLabel): model = self.data["models"]["item"] @@ -535,7 +535,7 @@ def toggleSection(self, checkState, sectionLabel): if item.isToggled != checkState and item.optional: self.__toggle_item(model, model.items.index(item)) - @QtCore.pyqtSlot(bool, str) + @QtCore.Slot(bool, str) def hideSection(self, hideState, sectionLabel): model = self.data["models"]["item"] @@ -549,7 +549,7 @@ def hideSection(self, hideState, sectionLabel): if item.itemType == "section" and item.name == sectionLabel: self.__hide_item(model, model.items.index(item), hideState) - @QtCore.pyqtSlot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result=QtCore.QVariant) def pluginData(self, index): models = self.data["models"] proxies = self.data["proxies"] @@ -559,7 +559,7 @@ def pluginData(self, index): source_index = source_qindex.row() return self.__item_data(models["item"], source_index) - @QtCore.pyqtSlot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result=QtCore.QVariant) def instanceData(self, index): models = self.data["models"] proxies = self.data["proxies"] @@ -569,7 +569,7 @@ def instanceData(self, index): source_index = source_qindex.row() return self.__item_data(models["item"], source_index) - @QtCore.pyqtSlot(int) + @QtCore.Slot(int) def togglePlugin(self, index): models = self.data["models"] proxies = self.data["proxies"] @@ -584,7 +584,7 @@ def togglePlugin(self, index): else: self.error.emit("Cannot toggle") - @QtCore.pyqtSlot(str, str, str, str) + @QtCore.Slot(str, str, str, str) def exclude(self, target, operation, role, value): """Exclude a `role` of `value` at `target` @@ -609,7 +609,7 @@ def exclude(self, target, operation, role, value): else: raise TypeError("operation must be either `add` or `remove`") - @QtCore.pyqtSlot() + @QtCore.Slot() def save(self): # Deprecated return @@ -733,12 +733,12 @@ def on_info(self, message): # Slots - @QtCore.pyqtSlot() + @QtCore.Slot() def stop(self): self.data["state"]["is_running"] = False self.stopping.emit() - @QtCore.pyqtSlot() + @QtCore.Slot() def reset(self): """Request that host re-discovers plug-ins and re-processes selectors @@ -885,7 +885,7 @@ def on_reset(): util.defer(self.host.reset, callback=on_reset) - @QtCore.pyqtSlot() + @QtCore.Slot() def publish(self): """Start asynchonous publishing @@ -942,7 +942,7 @@ def on_finished(): util.defer(get_data, callback=on_data_received) - @QtCore.pyqtSlot() + @QtCore.Slot() def validate(self): """Start asynchonous validation @@ -1102,7 +1102,7 @@ def on_finished(message=None): iterator = self.iterator(plugins, context) util.defer(lambda: next(iterator), callback=on_next) - @QtCore.pyqtSlot(int) + @QtCore.Slot(int) def repairPlugin(self, index): """ diff --git a/pyblish_qml/models.py b/pyblish_qml/models.py index 2eaa6d3a..db504ad3 100644 --- a/pyblish_qml/models.py +++ b/pyblish_qml/models.py @@ -2,7 +2,7 @@ import time import logging -from PyQt5 import QtCore +from Qt5 import QtCore from . import util, settings from .vendor import six @@ -117,7 +117,7 @@ def __new__(cls, name, bases, attrs): if key.startswith("__"): continue - notify = QtCore.pyqtSignal() + notify = QtCore.Signal() def set_data(key, value): def set_data(self, value): @@ -127,7 +127,7 @@ def set_data(self, value): return set_data attrs[key + "Changed"] = notify - attrs[key] = QtCore.pyqtProperty( + attrs[key] = QtCore.Property( type(value) if value is not None else QtCore.QVariant, fget=lambda self, k=key: getattr(self, cls.prefix + k, None), fset=set_data(key, value), @@ -145,7 +145,7 @@ class AbstractItem(QtCore.QObject): """ - __datachanged__ = QtCore.pyqtSignal(QtCore.QObject) + __datachanged__ = QtCore.Signal(QtCore.QObject) def __str__(self): return self.name @@ -199,7 +199,7 @@ def __init__(self, parent=None): super(AbstractModel, self).__init__(parent) self.items = util.ItemList(key="id") - @QtCore.pyqtSlot(int, result=QtCore.QObject) + @QtCore.Slot(int, result=QtCore.QObject) def item(self, index): return self.items[index] @@ -309,7 +309,7 @@ def reorder(self, context): self.endResetModel() - @QtCore.pyqtSlot(QtCore.QVariant) + @QtCore.Slot(QtCore.QVariant) def add_plugin(self, plugin): """Append `plugin` to model @@ -381,7 +381,7 @@ def add_plugin(self, plugin): item = self.add_item(item) self.plugins.append(item) - @QtCore.pyqtSlot(QtCore.QVariant) + @QtCore.Slot(QtCore.QVariant) def add_instance(self, instance): """Append `instance` to model @@ -445,7 +445,7 @@ def add_section(self, name): return item - @QtCore.pyqtSlot(QtCore.QVariant) + @QtCore.Slot(QtCore.QVariant) def add_context(self, context, label=None): """Append `context` to model @@ -604,7 +604,7 @@ def reset(self): class ResultModel(AbstractModel): - added = QtCore.pyqtSignal() + added = QtCore.Signal() def add_item(self, item): item_ = defaults["result"].copy() @@ -744,21 +744,21 @@ def __init__(self, source, excludes=None, includes=None, parent=None): self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - @QtCore.pyqtSlot(int, result=QtCore.QObject) + @QtCore.Slot(int, result=QtCore.QObject) def item(self, index): index = self.index(index, 0, QtCore.QModelIndex()) index = self.mapToSource(index) model = self.sourceModel() return model.items[index.row()] - @QtCore.pyqtSlot(str, result=QtCore.QObject) + @QtCore.Slot(str, result=QtCore.QObject) def itemByName(self, name): model = self.sourceModel() for item in model.items: if name == item.name: return item - @QtCore.pyqtSlot(str, str) + @QtCore.Slot(str, str) def add_exclusion(self, role, value): """Exclude item if `role` equals `value` @@ -770,7 +770,7 @@ def add_exclusion(self, role, value): self._add_rule(self.excludes, role, value) - @QtCore.pyqtSlot(str, str) + @QtCore.Slot(str, str) def remove_exclusion(self, role, value=None): """Remove exclusion rule @@ -795,11 +795,11 @@ def set_exclusion(self, rules): self._set_rules(self.excludes, rules) - @QtCore.pyqtSlot() + @QtCore.Slot() def clear_exclusion(self): self._clear_group(self.excludes) - @QtCore.pyqtSlot(str, str) + @QtCore.Slot(str, str) def add_inclusion(self, role, value): """Include item if `role` equals `value` @@ -811,7 +811,7 @@ def add_inclusion(self, role, value): self._add_rule(self.includes, role, value) - @QtCore.pyqtSlot(str, str) + @QtCore.Slot(str, str) def remove_inclusion(self, role, value=None): """Remove exclusion rule""" self._remove_rule(self.includes, role, value) @@ -819,7 +819,7 @@ def remove_inclusion(self, role, value=None): def set_inclusion(self, rules): self._set_rules(self.includes, rules) - @QtCore.pyqtSlot() + @QtCore.Slot() def clear_inclusion(self): self._clear_group(self.includes) @@ -885,6 +885,6 @@ def filterAcceptsRow(self, source_row, source_parent): return super(ProxyModel, self).filterAcceptsRow( source_row, source_parent) - @QtCore.pyqtSlot(result=int) + @QtCore.Slot(result=int) def rowCount(self, parent=QtCore.QModelIndex()): return super(ProxyModel, self).rowCount(parent) diff --git a/pyblish_qml/util.py b/pyblish_qml/util.py index eeb03eb7..9245986d 100644 --- a/pyblish_qml/util.py +++ b/pyblish_qml/util.py @@ -4,7 +4,7 @@ import traceback from functools import wraps -from PyQt5 import QtCore +from Qt5 import QtCore from .vendor import six @@ -146,7 +146,7 @@ def defer(target, args=None, kwargs=None, callback=None): class _defer(QtCore.QThread): - done = QtCore.pyqtSignal(QtCore.QVariant, arguments=["result"]) + done = QtCore.Signal(QtCore.QVariant, arguments=["result"]) def __init__(self, target, args=None, kwargs=None, callback=None): super(_defer, self).__init__() @@ -232,10 +232,10 @@ def format_text(text): return result -def pyqtConstantProperty(fget): - return QtCore.pyqtProperty(QtCore.QVariant, - fget=fget, - constant=True) +def qtConstantProperty(fget): + return QtCore.Property(QtCore.QVariant, + fget=fget, + constant=True) def SlotSentinel(*args): @@ -248,7 +248,7 @@ def SlotSentinel(*args): if len(args) == 0 or isinstance(args[0], types.FunctionType): args = [] - @QtCore.pyqtSlot(*args) + @QtCore.Slot(*args) def slotdecorator(func): @wraps(func) def wrapper(*args, **kwargs): From 8805c5f3f47729e1792b04fbf8391a2de89126d1 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sat, 1 Feb 2020 17:07:56 +0800 Subject: [PATCH 02/13] Implement `util.wait` to replace `PyQt5.QtTest.QSignalSpy` --- pyblish_qml/app.py | 6 +----- pyblish_qml/control.py | 10 ++++++++++ pyblish_qml/util.py | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 599a5e30..3a5f73a9 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -176,11 +176,7 @@ def show(self, client_settings=None): for state in ["ready", "finished"]): util.timer("ready") - ready = QtTest.QSignalSpy(self.controller.ready) - - count = len(ready) - ready.wait(1000) - if len(ready) != count + 1: + if not self.controller.is_ready(): print("Warning: Could not enter ready state") util.timer_end("ready", "Awaited statemachine for %.2f ms") diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 65ea5bbd..4d1460d1 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -107,6 +107,7 @@ def __init__(self, host, parent=None, targets=[]): }, "state": { "is_running": False, + "readyCount": 0, "current": None, "all": list(), @@ -125,6 +126,7 @@ def __init__(self, host, parent=None, targets=[]): self.info.connect(self.on_info) self.error.connect(self.on_error) self.finished.connect(self.on_finished) + self.ready.connect(self.on_ready) self.show.connect(self.on_show) # NOTE: Listeners to this signal are run in the main thread @@ -664,6 +666,11 @@ def comment_sync(self, comment): self.host.update(key="comment", value=comment) self.host.emit("commented", comment=comment) + def is_ready(self): + count = self.data["state"]["readyCount"] + util.wait(self.ready, 1000) + return self.data["state"]["readyCount"] == count + 1 + # Event handlers def on_commenting(self, comment): @@ -712,6 +719,9 @@ def on_state_changed(self, state): s.name for s in self.machine.configuration() ) + def on_ready(self): + self.data["state"]["readyCount"] += 1 + def on_finished(self): self.data["models"]["item"].reset_status() diff --git a/pyblish_qml/util.py b/pyblish_qml/util.py index 9245986d..7b68e341 100644 --- a/pyblish_qml/util.py +++ b/pyblish_qml/util.py @@ -196,6 +196,30 @@ def schedule(func, time, channel="default"): _jobs[channel] = timer +def wait(signal, timeout=5000): + """Wait until signal received + + Starts an event loop that runs until the given signal is received. + Optionally the event loop can return earlier on a timeout (milliseconds). + + Returns `True` if the signal was emitted at least once in timeout, + otherwise returns `False`. + + """ + loop = QtCore.QEventLoop() + + def on_signal(): + loop.exit(True) + + def on_timeout(): + loop.exit(False) + + signal.connect(on_signal) + QtCore.QTimer.singleShot(timeout, on_timeout) + + return loop.exec_() + + class Timer(object): """Time operations using this context manager From 849ed63fa1384f70b47aa906bae05b45e836f970 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sat, 1 Feb 2020 19:00:22 +0800 Subject: [PATCH 03/13] Fix var type changed in PySide signal --- pyblish_qml/util.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyblish_qml/util.py b/pyblish_qml/util.py index 7b68e341..0e50d7e9 100644 --- a/pyblish_qml/util.py +++ b/pyblish_qml/util.py @@ -146,7 +146,18 @@ def defer(target, args=None, kwargs=None, callback=None): class _defer(QtCore.QThread): - done = QtCore.Signal(QtCore.QVariant, arguments=["result"]) + done = QtCore.Signal(object, arguments=["result"]) + # (NOTE) The type `object` is a workaround for `QVaraint` + # + # When using PySide as Qt binding, QVaraint was not able to handle + # custom type properly. + # + # For example, like `pyblish.api.Context` which is a subclass of + # `list`, the signal receiver will get an instance of `list` instead + # of `pyblish.api.Context` when using PySide. But if register it with + # type `object` instead of `QVaraint`, the variable type will stay as + # what it was in both PyQt and PySide. + # def __init__(self, target, args=None, kwargs=None, callback=None): super(_defer, self).__init__() From 3826b6fdc53a5f82fdf31cdb5d2f36996055c641 Mon Sep 17 00:00:00 2001 From: David Lai Date: Mon, 3 Feb 2020 03:05:57 +0800 Subject: [PATCH 04/13] Change QtCore.QVariant to "QVariant" for compatibility PySide does not have `QtCore.QVariant`, but both PyQt and PySide able to accept "QVariant". --- pyblish_qml/app.py | 4 ++-- pyblish_qml/control.py | 6 +++--- pyblish_qml/models.py | 10 +++++----- pyblish_qml/util.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 3a5f73a9..9723b82d 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -64,13 +64,13 @@ class Application(QtGui.QGuiApplication): """ - shown = QtCore.Signal(QtCore.QVariant) + shown = QtCore.Signal("QVariant") hidden = QtCore.Signal() quitted = QtCore.Signal() published = QtCore.Signal() validated = QtCore.Signal() - targeted = QtCore.Signal(QtCore.QVariant) + targeted = QtCore.Signal("QVariant") risen = QtCore.Signal() inFocused = QtCore.Signal() diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index 4d1460d1..fbdbd40d 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -379,7 +379,7 @@ def iterator(self, plugins, context): yield result - @QtCore.Slot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result="QVariant") def getPluginActions(self, index): """Return actions from plug-in at `index` @@ -551,7 +551,7 @@ def hideSection(self, hideState, sectionLabel): if item.itemType == "section" and item.name == sectionLabel: self.__hide_item(model, model.items.index(item), hideState) - @QtCore.Slot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result="QVariant") def pluginData(self, index): models = self.data["models"] proxies = self.data["proxies"] @@ -561,7 +561,7 @@ def pluginData(self, index): source_index = source_qindex.row() return self.__item_data(models["item"], source_index) - @QtCore.Slot(int, result=QtCore.QVariant) + @QtCore.Slot(int, result="QVariant") def instanceData(self, index): models = self.data["models"] proxies = self.data["proxies"] diff --git a/pyblish_qml/models.py b/pyblish_qml/models.py index db504ad3..e09fb6a1 100644 --- a/pyblish_qml/models.py +++ b/pyblish_qml/models.py @@ -128,7 +128,7 @@ def set_data(self, value): attrs[key + "Changed"] = notify attrs[key] = QtCore.Property( - type(value) if value is not None else QtCore.QVariant, + type(value) if value is not None else "QVariant", fget=lambda self, k=key: getattr(self, cls.prefix + k, None), fset=set_data(key, value), notify=notify) @@ -247,7 +247,7 @@ def data(self, index, role=QtCore.Qt.DisplayRole): except Exception: pass - return QtCore.QVariant() + return Item() def roleNames(self): return { @@ -309,7 +309,7 @@ def reorder(self, context): self.endResetModel() - @QtCore.Slot(QtCore.QVariant) + @QtCore.Slot("QVariant") def add_plugin(self, plugin): """Append `plugin` to model @@ -381,7 +381,7 @@ def add_plugin(self, plugin): item = self.add_item(item) self.plugins.append(item) - @QtCore.Slot(QtCore.QVariant) + @QtCore.Slot("QVariant") def add_instance(self, instance): """Append `instance` to model @@ -445,7 +445,7 @@ def add_section(self, name): return item - @QtCore.Slot(QtCore.QVariant) + @QtCore.Slot("QVariant") def add_context(self, context, label=None): """Append `context` to model diff --git a/pyblish_qml/util.py b/pyblish_qml/util.py index 0e50d7e9..e64575ca 100644 --- a/pyblish_qml/util.py +++ b/pyblish_qml/util.py @@ -268,7 +268,7 @@ def format_text(text): def qtConstantProperty(fget): - return QtCore.Property(QtCore.QVariant, + return QtCore.Property("QVariant", fget=fget, constant=True) From 0f624c6babf3064f32ba16b1c6a31775a9b858b2 Mon Sep 17 00:00:00 2001 From: David Lai Date: Mon, 3 Feb 2020 03:06:34 +0800 Subject: [PATCH 05/13] Vendorize Qt5.py --- pyblish_qml/app.py | 4 +- pyblish_qml/control.py | 2 +- pyblish_qml/models.py | 3 +- pyblish_qml/util.py | 2 +- pyblish_qml/vendor/Qt5.py | 83 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 pyblish_qml/vendor/Qt5.py diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 9723b82d..ee910046 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -8,11 +8,9 @@ import traceback import threading -# Dependencies -from Qt5 import QtCore, QtGui, QtQuick - # Local libraries from . import util, compat, control, settings, ipc +from .vendor.Qt5 import QtCore, QtGui, QtQuick MODULE_DIR = os.path.dirname(__file__) QML_IMPORT_DIR = os.path.join(MODULE_DIR, "qml") diff --git a/pyblish_qml/control.py b/pyblish_qml/control.py index fbdbd40d..bf7f062a 100644 --- a/pyblish_qml/control.py +++ b/pyblish_qml/control.py @@ -4,11 +4,11 @@ import collections # Dependencies -from Qt5 import QtCore import pyblish.logic # Local libraries from . import util, models, version, settings +from .vendor.Qt5 import QtCore qtproperty = util.qtConstantProperty diff --git a/pyblish_qml/models.py b/pyblish_qml/models.py index e09fb6a1..f00f1727 100644 --- a/pyblish_qml/models.py +++ b/pyblish_qml/models.py @@ -2,10 +2,9 @@ import time import logging -from Qt5 import QtCore - from . import util, settings from .vendor import six +from .vendor.Qt5 import QtCore defaults = { diff --git a/pyblish_qml/util.py b/pyblish_qml/util.py index e64575ca..ec11beb4 100644 --- a/pyblish_qml/util.py +++ b/pyblish_qml/util.py @@ -4,9 +4,9 @@ import traceback from functools import wraps -from Qt5 import QtCore from .vendor import six +from .vendor.Qt5 import QtCore _timers = {} _defer_threads = [] diff --git a/pyblish_qml/vendor/Qt5.py b/pyblish_qml/vendor/Qt5.py new file mode 100644 index 00000000..a24b2572 --- /dev/null +++ b/pyblish_qml/vendor/Qt5.py @@ -0,0 +1,83 @@ +import os +import sys +import types + +__version__ = "0.2.0.b2" + +QT_VERBOSE = bool(os.getenv("QT_VERBOSE")) +QT_PREFERRED_BINDING = os.environ.get("QT_PREFERRED_BINDING") +QtCompat = types.ModuleType("QtCompat") + + +def _log(text): + if QT_VERBOSE: + sys.stdout.write(text + "\n") + + +try: + from PySide2 import ( + QtWidgets, + QtCore, + QtGui, + QtQml, + QtQuick, + QtMultimedia, + QtOpenGL, + ) + + from shiboken2 import wrapInstance, getCppPointer + QtCompat.wrapInstance = wrapInstance + QtCompat.getCppPointer = getCppPointer + + try: + from PySide2 import QtUiTools + QtCompat.loadUi = QtUiTools.QUiLoader + + except ImportError: + _log("QtUiTools not provided.") + + +except ImportError: + try: + from PyQt5 import ( + QtWidgets, + QtCore, + QtGui, + QtQml, + QtQuick, + QtMultimedia, + QtOpenGL, + ) + + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + QtCore.Property = QtCore.pyqtProperty + + from sip import wrapinstance, unwrapinstance + QtCompat.wrapInstance = wrapinstance + QtCompat.getCppPointer = unwrapinstance + + try: + from PyQt5 import uic + QtCompat.loadUi = uic.loadUi + except ImportError: + _log("uic not provided.") + + except ImportError: + + # Used during tests and installers + if QT_PREFERRED_BINDING == "None": + _log("No binding found") + else: + raise + +__all__ = [ + "QtWidgets", + "QtCore", + "QtGui", + "QtQml", + "QtQuick", + "QtMultimedia", + "QtCompat", + "QtOpenGL", +] From ae04b481ee52583b9af4d0e4008c77f60f50ab8d Mon Sep 17 00:00:00 2001 From: David Lai Date: Mon, 3 Feb 2020 03:06:56 +0800 Subject: [PATCH 06/13] Fix missing window frame when using PySide2 --- pyblish_qml/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index ee910046..b8efe574 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -208,12 +208,14 @@ def inFocus(self): previous_flags = self.window.flags() self.window.setFlags(previous_flags | QtCore.Qt.WindowStaysOnTopHint) + self.window.setFlags(previous_flags) def outFocus(self): """Remove GUI on-top flag""" previous_flags = self.window.flags() self.window.setFlags(previous_flags ^ QtCore.Qt.WindowStaysOnTopHint) + self.window.setFlags(previous_flags) def publish(self): """Fire up the publish sequence""" From 14b58f9e930821d5e7962a5cfbc2f36466e8b76e Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Feb 2020 18:57:45 +0800 Subject: [PATCH 07/13] Change `Item()` to "QVariant" for consistency --- pyblish_qml/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyblish_qml/models.py b/pyblish_qml/models.py index f00f1727..2f4c75b5 100644 --- a/pyblish_qml/models.py +++ b/pyblish_qml/models.py @@ -246,7 +246,7 @@ def data(self, index, role=QtCore.Qt.DisplayRole): except Exception: pass - return Item() + return "QVariant" def roleNames(self): return { From 9205fddafed04f9beea86fe7a7ae438130e79471 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Feb 2020 20:33:34 +0800 Subject: [PATCH 08/13] Fix CI test --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c82476b6..65ea7dcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,7 @@ MAINTAINER marcus@abstractfactory.io RUN apt-get update && apt-get install -y \ build-essential \ git \ - python3-pyqt5 \ - python3-pyqt5.qtquick \ + python3-pyqt5* \ python3-pip \ python3-nose && \ pip3 install \ From cc9f0f46e0e097d5efb0607b0c646156ba83dd31 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Feb 2020 21:44:44 +0800 Subject: [PATCH 09/13] Fix font weird appearance on first run --- pyblish_qml/qml/CommentBox.qml | 2 +- pyblish_qml/qml/Pyblish/Label.qml | 2 +- pyblish_qml/qml/Pyblish/SpinBox.qml | 2 +- pyblish_qml/qml/Pyblish/TextField.qml | 2 +- pyblish_qml/qml/main.qml | 5 +++++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pyblish_qml/qml/CommentBox.qml b/pyblish_qml/qml/CommentBox.qml index 19bd218a..075d5ff2 100644 --- a/pyblish_qml/qml/CommentBox.qml +++ b/pyblish_qml/qml/CommentBox.qml @@ -63,7 +63,7 @@ Rectangle { visible: parent.length == 0 } - font.family: "Open Sans" + font.family: mainFont.name font.weight: Font.Normal KeyNavigation.priority: KeyNavigation.BeforeItem diff --git a/pyblish_qml/qml/Pyblish/Label.qml b/pyblish_qml/qml/Pyblish/Label.qml index 8849099e..df46d48e 100644 --- a/pyblish_qml/qml/Pyblish/Label.qml +++ b/pyblish_qml/qml/Pyblish/Label.qml @@ -80,7 +80,7 @@ Text { property var fontInfo: fontStyles[style] font.pixelSize: fontInfo.size * sizeMult - font.family: "Open Sans" + font.family: mainFont.name font.weight: { var weight = fontInfo.font diff --git a/pyblish_qml/qml/Pyblish/SpinBox.qml b/pyblish_qml/qml/Pyblish/SpinBox.qml index 8d6ba685..29238458 100644 --- a/pyblish_qml/qml/Pyblish/SpinBox.qml +++ b/pyblish_qml/qml/Pyblish/SpinBox.qml @@ -7,7 +7,7 @@ Control.SpinBox { style: SpinBoxStyle { background: Item {} - font.family: "Open Sans" + font.family: mainFont.name textColor: Theme.dark.textColor selectionColor: Theme.accentColor diff --git a/pyblish_qml/qml/Pyblish/TextField.qml b/pyblish_qml/qml/Pyblish/TextField.qml index ac1b8558..1119bf36 100644 --- a/pyblish_qml/qml/Pyblish/TextField.qml +++ b/pyblish_qml/qml/Pyblish/TextField.qml @@ -78,7 +78,7 @@ TextEdit { property var fontInfo: fontStyles[style] font.pixelSize: fontInfo.size * sizeMult - font.family: "Open Sans" + font.family: mainFont.name font.weight: { var weight = fontInfo.font diff --git a/pyblish_qml/qml/main.qml b/pyblish_qml/qml/main.qml index 56b64241..f1842118 100644 --- a/pyblish_qml/qml/main.qml +++ b/pyblish_qml/qml/main.qml @@ -17,6 +17,11 @@ import QtQuick 2.3 Rectangle { color: Qt.rgba(0.3, 0.3, 0.3) + FontLoader { + id: mainFont + name: "Open Sans" + } + Loader { id: loader anchors.fill: parent From 346c5c8dcb876d56208cb63087f9776fe9bb8a95 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Feb 2020 21:46:08 +0800 Subject: [PATCH 10/13] Make text rendering consistent between bindings --- pyblish_qml/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index b8efe574..4995f1b0 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -112,6 +112,7 @@ def __init__(self, source, targets=[]): self.outFocused.connect(self.outFocus) window.setSource(QtCore.QUrl.fromLocalFile(source)) + window.setTextRenderType(QtQuick.QQuickWindow.NativeTextRendering) def on_status_changed(self, status): if status == QtQuick.QQuickView.Error: From 465d5d9a2e7192977e5a326d8a29edac4b41aa50 Mon Sep 17 00:00:00 2001 From: davidlatwe Date: Mon, 3 Feb 2020 21:46:08 +0800 Subject: [PATCH 11/13] Revert "Make text rendering consistent between bindings" This reverts commit 346c5c8dcb876d56208cb63087f9776fe9bb8a95. --- pyblish_qml/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index 4995f1b0..b8efe574 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -112,7 +112,6 @@ def __init__(self, source, targets=[]): self.outFocused.connect(self.outFocus) window.setSource(QtCore.QUrl.fromLocalFile(source)) - window.setTextRenderType(QtQuick.QQuickWindow.NativeTextRendering) def on_status_changed(self, status): if status == QtQuick.QQuickView.Error: From 900d739e51621e3e614c61d8fc8239927e476d67 Mon Sep 17 00:00:00 2001 From: David Lai Date: Wed, 5 Feb 2020 03:31:02 +0800 Subject: [PATCH 12/13] Set parent to Controller to avoid TypeError on quit See https://gist.github.com/davidlatwe/44d6236570a11ebb869682a8d0b4f661 --- pyblish_qml/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyblish_qml/app.py b/pyblish_qml/app.py index b8efe574..acab20b6 100644 --- a/pyblish_qml/app.py +++ b/pyblish_qml/app.py @@ -86,7 +86,7 @@ def __init__(self, source, targets=[]): engine.addImportPath(QML_IMPORT_DIR) host = ipc.client.Proxy() - controller = control.Controller(host, targets=targets) + controller = control.Controller(host, targets=targets, parent=window) controller.finished.connect(lambda: window.alert(0)) context = engine.rootContext() From 4b1cdd61566456f51cf969efb43a9c92caefd9a5 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sun, 9 Feb 2020 18:00:41 +0800 Subject: [PATCH 13/13] Version bump --- pyblish_qml/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyblish_qml/version.py b/pyblish_qml/version.py index b81ab146..170991ed 100644 --- a/pyblish_qml/version.py +++ b/pyblish_qml/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 1 -VERSION_MINOR = 10 -VERSION_PATCH = 6 +VERSION_MINOR = 11 +VERSION_PATCH = 0 version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) version = '%i.%i.%i' % version_info