print(obj) if __name__ == '__main__': import sys from Curves import BezierCurve from Generators import JsonGenerator app = QGuiApplication(sys.argv) qmlRegisterType(ConsoleOutput, 'PyConsole', 1, 0, 'PyConsole') qmlRegisterType(BezierCurve, 'Curves', 1, 0, 'BezierCurve') qmlRegisterType(JsonGenerator, 'Generators', 1, 0, 'JsonGenerator') view = QQuickView() context = view.rootContext() view.engine().quit.connect(app.quit) view.setResizeMode(QQuickView.SizeRootObjectToView) view.setSource(QUrl.fromLocalFile('../view/visionai.qml')) view.show() for error in view.errors(): print(error.toString()) status = app.exec_() for error in view.errors(): print(error.toString()) sys.exit(status)
if __name__ == '__main__': import sys from Curves import BezierCurve from Generators import JsonGenerator app = QGuiApplication(sys.argv) qmlRegisterType(ConsoleOutput, 'PyConsole', 1, 0, 'PyConsole') qmlRegisterType(BezierCurve, 'Curves', 1, 0, 'BezierCurve') qmlRegisterType(JsonGenerator, 'Generators', 1, 0, 'JsonGenerator') view = QQuickView() context = view.rootContext() view.engine().quit.connect(app.quit) view.setResizeMode(QQuickView.SizeRootObjectToView) view.setSource( QUrl.fromLocalFile('../view/visionai.qml') ) view.show() for error in view.errors(): print(error.toString()) status = app.exec_() for error in view.errors(): print(error.toString()) sys.exit(status)
class PreviewTab(QWidget): """Preview of QML component given by 'source' argument. If any file in the source's directory or one of its subdirectories is changed, the view is updated unless paused. Potential errors in the QML code are displayed in red.""" def __init__(self, source=None, parent=None, import_paths=None): super().__init__(parent=parent) self.updating_paused = False self.qml_view = QQuickView() engine = self.qml_view.engine() import_paths = import_paths or [] for import_path in import_paths: engine.addImportPath(import_path) # idea from # https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer self.container = QWidget.createWindowContainer(self.qml_view, self) self.error_info = QLabel() self.error_info.setWordWrap(True) self.qml_source = QUrl() if source is None else QUrl(source) self.update_source() self.pause_button = QPushButton("Pause") self.pause_button.setCheckable(True) layout = QVBoxLayout() layout.setAlignment(Qt.AlignCenter) layout.addWidget(self.error_info) layout.addWidget(self.container) layout.addWidget(self.pause_button) self.setLayout(layout) # Observations using the QFileSystemWatcher in various settings: # A) fileChanged signal and qml file paths # Collected all *.qml files in the directory of the source and all # subdirectories and added to the watcher. Now the first change would # trigger the signal but none of the following # B) additionally connecting directoryChanged signal # same issue # C) both signals, (sub)directory file paths # Collected all subdirectories of the source directory and added it to # the watcher, along with the source directory itself. Works as # expected # D) directoryChanged signal, (sub)directory file paths # same as C) without fileChanged signal, works as expected # Eventual solution: D # This implementation also helped me: # https://github.com/penk/qml-livereload/blob/master/main.cpp self.watcher = QFileSystemWatcher() source_dir = os.path.dirname(source) source_paths = glob.glob(os.path.join(source_dir, "*/"), recursive=True) source_paths.append(source_dir) failed_paths = self.watcher.addPaths(source_paths) if failed_paths: print("Failed to watch paths: {}".format(", ".join(failed_paths))) self.pause_button.clicked.connect(self.toggle_updating) self.qml_view.statusChanged.connect(self.check_status) self.watcher.directoryChanged.connect(self.update_source) def toggle_updating(self, clicked): """Callback for pause button.""" self.pause_button.setText("Resume" if clicked else "Pause") self.updating_paused = clicked # Update when resuming in case of the source having changed if not self.updating_paused: self.update_source() def update_source(self, _=None): """Method and callback to update the QML view source. The second argument is required to have a slot matching the directoryChanged() interface. This immediately returns if updating is paused. """ if self.updating_paused: return # idea from # https://stackoverflow.com/questions/17337493/how-to-reload-qml-file-to-qquickview self.qml_view.setSource(QUrl()) self.qml_view.engine().clearComponentCache() # avoid error: No such file or directory QThread.msleep(50) self.qml_view.setSource(self.qml_source) self.container.setMinimumSize(self.qml_view.size()) self.container.setMaximumSize(self.qml_view.size()) # avoid error label making the window too wide self.error_info.setMaximumWidth(self.container.maximumWidth()) def check_status(self, status): if status == QQuickView.Error: self.error_info.setText("<font color='red'>{}</font>".format( self.qml_view.errors()[-1].toString())) else: self.error_info.clear() def shutdown(self): """Shutdown tab to prepare for removal. Manually delete QML related members to free resources. Otherwise error messages are printed even after closing the tab, indicating that the QML engine still runs in the background. """ del self.container del self.qml_view