class SmartControlApplication(QGuiApplication): _instance = None WINDOW_ICON = "smart-control.png" DEFAULT_THEME = "default" MAIN_QML = "main.qml" def __init__(self, **kwargs): if sys.platform == "win32": if hasattr(sys, "frozen"): QCoreApplication.addLibraryPath(os.path.join(os.path.abspath(os.path.dirname(sys.executable)), "PyQt5", "plugins")) else: import site for dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(dir, "PyQt5", "plugins")) super().__init__(sys.argv, **kwargs) self.setApplicationVersion(version) self._engine = QQmlApplicationEngine() self._internationalization = Internationalization() self._theme = Theme() self._printerConnectionManager = PrinterConnectionManager() @classmethod def instance(cls): if SmartControlApplication._instance is None: SmartControlApplication._instance = cls() return SmartControlApplication._instance def run(self): self._internationalization.load(locale.getdefaultlocale()[0]) self._theme.load(SmartControlApplication.DEFAULT_THEME) self._printerConnectionManager.start() self.aboutToQuit.connect(self._onClose) self.setWindowIcon(QIcon(Resources.icon(SmartControlApplication.WINDOW_ICON))) Binder(self._internationalization, self._theme, self._printerConnectionManager).register() self._engine = QQmlApplicationEngine() if sys.platform == "win32": self._engine.addImportPath(os.path.join(os.path.abspath(os.path.dirname(sys.executable)), "qml")) self._engine.load(Resources.qml(SmartControlApplication.MAIN_QML)) sys.exit(self.exec_()) def _onClose(self): self._printerConnectionManager.stop()
class QtApplication(QApplication, Application): def __init__(self, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self._plugins_loaded = False #Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(Application.getInstallPrefix(), "Resources", "qml")) self.setAttribute(Qt.AA_UseDesktopOpenGL) try: self._splash = self._createSplashScreen() except FileNotFoundError: self._splash = None else: self._splash.show() self.processEvents() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) if self._version_upgrade_manager: self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) upgraded = self._version_upgrade_manager.upgrade() if upgraded: preferences = UM.Preferences.getInstance() #Preferences might have changed. Load them again. #Note that the language can't be updated, so that will always revert to English. try: preferences.readFromFile(Resources.getPath(Resources.Preferences, self._application_name + ".cfg")) except FileNotFoundError: pass self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file) except FileNotFoundError: pass def run(self): pass def hideMessage(self, message): with self._message_lock: if message in self._visible_messages: self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message): with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setTimer(QTimer()) self.visibleMessageAdded.emit(message) def setMainQml(self, path): self._main_qml = path def initializeEngine(self): # TODO: Document native/qml import trickery Bindings.register() self._engine = QQmlApplicationEngine() for path in self._qml_import_paths: self._engine.addImportPath(path) if not hasattr(sys, "frozen"): self._engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self.registerObjects(self._engine) self._engine.load(self._main_qml) self.engineCreatedSignal.emit() engineCreatedSignal = Signal() def isShuttingDown(self): return self._shutting_down def registerObjects(self, engine): pass def getRenderer(self): if not self._renderer: self._renderer = QtRenderer() return self._renderer def addCommandLineOptions(self, parser): parser.add_argument("--disable-textures", dest="disable-textures", action="store_true", default=False, help="Disable Qt texture loading as a workaround for certain crashes.") # Overridden from QApplication::setApplicationName to call our internal setApplicationName def setApplicationName(self, name): Application.setApplicationName(self, name) mainWindowChanged = Signal() def getMainWindow(self): return self._main_window def setMainWindow(self, window): if window != self._main_window: self._main_window = window self.mainWindowChanged.emit() # Handle a function that should be called later. def functionEvent(self, event): e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event): if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self): Logger.log("d", "Shutting down %s", self.getApplicationName()) self._shutting_down = True try: Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, self.getApplicationName() + ".cfg")) except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) self.quit() ## Load a Qt translation catalog. # # This method will locate, load and install a Qt message catalog that can be used # by Qt's translation system, like qsTr() in QML files. # # \param file The file name to load, without extension. It will be searched for in # the i18nLocation Resources directory. If it can not be found a warning # will be logged but no error will be thrown. # \param language The language to load translations for. This can be any valid language code # or 'default' in which case the language is looked up based on system locale. # If the specified language can not be found, this method will fall back to # loading the english translations file. # # \note When `language` is `default`, the language to load can be changed with the # environment variable "LANGUAGE". def loadQtTranslation(self, file, language = "default"): #TODO Add support for specifying a language from preferences path = None if language == "default": path = self._getDefaultLanguage(file) else: path = Resources.getPath(Resources.i18n, language, "LC_MESSAGES", file + ".qm") # If all else fails, fall back to english. if not path: Logger.log("w", "Could not find any translations matching {0} for file {1}, falling back to english".format(language, file)) try: path = Resources.getPath(Resources.i18n, "en", "LC_MESSAGES", file + ".qm") except FileNotFoundError: Logger.log("w", "Could not find English translations for file {0}. Switching to developer english.".format(file)) return translator = QTranslator() if not translator.load(path): Logger.log("e", "Unable to load translations %s", file) return # Store a reference to the translator. # This prevents the translator from being destroyed before Qt has a chance to use it. self._translators[file] = translator # Finally, install the translator so Qt can use it. self.installTranslator(translator) ## Display text on the splash screen. def showSplashMessage(self, message): if self._splash: self._splash.showMessage(message , Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() ## Close the splash screen after the application has started. def closeSplash(self): if self._splash: self._splash.close() self._splash = None def _createSplashScreen(self): return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _getDefaultLanguage(self, file): # If we have a language override set in the environment, try and use that. lang = os.getenv("URANIUM_LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # Else, try and get the current language from preferences lang = Preferences.getInstance().getValue("general/language") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If none of those are set, try to use the environment's LANGUAGE variable. lang = os.getenv("LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If looking up the language from the enviroment or preferences fails, try and use Qt's system locale instead. locale = QLocale.system() # First, try and find a directory for any of the provided languages for lang in locale.uiLanguages(): try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If that fails, see if we can extract a language "class" from the # preferred language. This will turn "en-GB" into "en" for example. lang = locale.uiLanguages()[0] lang = lang[0:lang.find("-")] try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass return None
class QtApplication(QApplication, Application, SignalEmitter): def __init__(self, **kwargs): plugin_path = "" if sys.platform == "win32": plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self._main_qml = "main.qml" self._engine = None self._renderer = None try: self._splash = QSplashScreen(QPixmap(Resources.getPath(Resources.ImagesLocation, self.getApplicationName() + ".png"))) except FileNotFoundError: self._splash = None else: self._splash.show() self.processEvents() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("Splash screen message", "Loading plugins...")) self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.showSplashMessage(i18n_catalog.i18nc("Splash screen message", "Loading machines...")) self.loadMachines() self.showSplashMessage(i18n_catalog.i18nc("Splash screen message", "Loading preferences...")) try: file = Resources.getPath(Resources.PreferencesLocation, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file) except FileNotFoundError: pass self._translators = {} self.showSplashMessage(i18n_catalog.i18nc("Splash screen message", "Loading translations...")) self.loadQtTranslation("uranium_qt") self.loadQtTranslation(self.getApplicationName() + "_qt") def run(self): pass def hideMessage(self, message): with self._message_lock: if message in self._visible_messages: self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message): with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setTimer(QTimer()) self.visibleMessageAdded.emit(message) pass def setMainQml(self, path): self._main_qml = path def initializeEngine(self): # TODO: Document native/qml import trickery Bindings.register() self._engine = QQmlApplicationEngine() self.engineCreatedSignal.emit() self._engine.addImportPath(os.path.join(os.path.dirname(sys.executable), "qml")) self._engine.addImportPath(os.path.join(Application.getInstallPrefix(), "Resources", "qml")) if not hasattr(sys, "frozen"): self._engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self.registerObjects(self._engine) self._engine.load(self._main_qml) engineCreatedSignal = Signal() def registerObjects(self, engine): pass def getRenderer(self): if not self._renderer: self._renderer = QtGL2Renderer() return self._renderer # Overridden from QApplication::setApplicationName to call our internal setApplicationName def setApplicationName(self, name): Application.setApplicationName(self, name) # Handle a function that should be called later. def functionEvent(self, event): e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event): if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self): self.getBackend().close() self.quit() self.saveMachines() Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.PreferencesLocation, self.getApplicationName() + ".cfg")) ## Load a Qt translation catalog. # # This method will locate, load and install a Qt message catalog that can be used # by Qt's translation system, like qsTr() in QML files. # # \param file The file name to load, without extension. It will be searched for in # the i18nLocation Resources directory. If it can not be found a warning # will be logged but no error will be thrown. # \param language The language to load translations for. This can be any valid language code # or 'default' in which case the language is looked up based on system locale. # If the specified language can not be found, this method will fall back to # loading the english translations file. # # \note When `language` is `default`, the language to load can be changed with the # environment variable "LANGUAGE". def loadQtTranslation(self, file, language = "default"): #TODO Add support for specifying a language from preferences path = None if language == "default": # If we have a language set in the environment, try and use that. lang = os.getenv("LANGUAGE") if lang: try: path = Resources.getPath(Resources.i18nLocation, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: path = None # If looking up the language from the enviroment fails, try and use Qt's system locale instead. if not path: locale = QLocale.system() # First, try and find a directory for any of the provided languages for lang in locale.uiLanguages(): try: path = Resources.getPath(Resources.i18nLocation, lang, "LC_MESSAGES", file + ".qm") language = lang except FileNotFoundError: pass else: break # If that fails, see if we can extract a language "class" from the # preferred language. This will turn "en-GB" into "en" for example. if not path: lang = locale.uiLanguages()[0] lang = lang[0:lang.find("-")] try: path = Resources.getPath(Resources.i18nLocation, lang, "LC_MESSAGES", file + ".qm") language = lang except FileNotFoundError: pass else: path = Resources.getPath(Resources.i18nLocation, language, "LC_MESSAGES", file + ".qm") # If all else fails, fall back to english. if not path: Logger.log("w", "Could not find any translations matching {0} for file {1}, falling back to english".format(language, file)) try: path = Resources.getPath(Resources.i18nLocation, "en", "LC_MESSAGES", file + ".qm") except FileNotFoundError: Logger.log("w", "Could not find English translations for file {0}. Switching to developer english.".format(file)) return translator = QTranslator() if not translator.load(path): Logger.log("e", "Unable to load translations %s", file) return # Store a reference to the translator. # This prevents the translator from being destroyed before Qt has a chance to use it. self._translators[file] = translator # Finally, install the translator so Qt can use it. self.installTranslator(translator) ## Display text on the splash screen. def showSplashMessage(self, message): if self._splash: self._splash.showMessage(message , Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() ## Close the splash screen after the application has started. def closeSplash(self): if self._splash: self._splash.close() self._splash = None
class QtApplication(QApplication, Application): pluginsLoaded = Signal() applicationRunning = Signal() def __init__(self, tray_icon_name: str = None, **kwargs) -> None: plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(self.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) # use Qt Quick Scene Graph "basic" render loop os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) # type: ignore self._qml_import_paths = [] #type: List[str] self._main_qml = "main.qml" #type: str self._qml_engine = None #type: Optional[QQmlApplicationEngine] self._main_window = None #type: Optional[MainWindow] self._tray_icon_name = tray_icon_name #type: Optional[str] self._tray_icon = None #type: Optional[str] self._tray_icon_widget = None #type: Optional[QSystemTrayIcon] self._theme = None #type: Optional[Theme] self._renderer = None #type: Optional[QtRenderer] self._job_queue = None #type: Optional[JobQueue] self._version_upgrade_manager = None #type: Optional[VersionUpgradeManager] self._is_shutting_down = False #type: bool self._recent_files = [] #type: List[QUrl] self._configuration_error_message = None #type: Optional[ConfigurationErrorMessage] def addCommandLineOptions(self) -> None: super().addCommandLineOptions() # This flag is used by QApplication. We don't process it. self._cli_parser.add_argument("-qmljsdebugger", help = "For Qt's QML debugger compatibility") def initialize(self) -> None: super().initialize() # Initialize the package manager to remove and install scheduled packages. self._package_manager = self._package_manager_class(self, parent = self) self._mesh_file_handler = MeshFileHandler(self) #type: MeshFileHandler self._workspace_file_handler = WorkspaceFileHandler(self) #type: WorkspaceFileHandler # Remove this and you will get Windows 95 style for all widgets if you are using Qt 5.10+ self.setStyle("fusion") self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion() if major_version is None and minor_version is None and profile is None and not self.getIsHeadLess(): Logger.log("e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting") QMessageBox.critical(None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers.") sys.exit(1) else: opengl_version_str = OpenGLContext.versionAsText(major_version, minor_version, profile) Logger.log("d", "Detected most suitable OpenGL context version: %s", opengl_version_str) if not self.getIsHeadLess(): OpenGLContext.setDefaultFormat(major_version, minor_version, profile = profile) self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(self.getInstallPrefix(), "Resources", "qml")) Logger.log("i", "Initializing job queue ...") self._job_queue = JobQueue() self._job_queue.jobFinished.connect(self._onJobFinished) Logger.log("i", "Initializing version upgrade manager ...") self._version_upgrade_manager = VersionUpgradeManager(self) def startSplashWindowPhase(self) -> None: super().startSplashWindowPhase() self._package_manager.initialize() # Read preferences here (upgrade won't work) to get the language in use, so the splash window can be shown in # the correct language. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(preferences_filename) except FileNotFoundError: Logger.log("i", "Preferences file not found, ignore and use default language '%s'", self._default_language) signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self._configuration_error_message = ConfigurationErrorMessage(self, i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."), lifetime = 0, title = i18n_catalog.i18nc("@info:title", "Configuration errors") ) # Remove, install, and then loading plugins self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) # Remove and install the plugins that have been scheduled self._plugin_registry.initializeBeforePluginsAreLoaded() self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) with self._container_registry.lockFile(): VersionUpgradeManager.getInstance().upgrade() # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") with open(preferences_filename, "r", encoding = "utf-8") as f: serialized = f.read() # This performs the upgrade for Preferences self._preferences.deserialize(serialized) self._preferences.setValue("general/plugins_to_remove", "") self._preferences.writeToFile(preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file cannot be found, will use default values") # Force the configuration file to be written again since the list of plugins to remove maybe changed self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: self._preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(self._preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file '%s' cannot be found, will use default values", self._preferences_filename) self._preferences_filename = Resources.getStoragePath(Resources.Preferences, self._app_name + ".cfg") # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file, # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins, # so we need to reset those values AFTER the Preferences file is loaded. self._plugin_registry.initializeAfterPluginsAreLoaded() # Check if we have just updated from an older version self._preferences.addPreference("general/last_run_version", "") last_run_version_str = self._preferences.getValue("general/last_run_version") if not last_run_version_str: last_run_version_str = self._version last_run_version = Version(last_run_version_str) current_version = Version(self._version) if last_run_version < current_version: self._just_updated_from_old_version = True self._preferences.setValue("general/last_run_version", str(current_version)) self._preferences.writeToFile(self._preferences_filename) # Preferences: recent files self._preferences.addPreference("%s/recent_files" % self._app_name, "") file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) if not self.getIsHeadLess(): # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None if self._tray_icon_name: self._tray_icon = QIcon(Resources.getPath(Resources.Images, self._tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) def initializeEngine(self) -> None: # TODO: Document native/qml import trickery self._qml_engine = QQmlApplicationEngine(self) self._qml_engine.setOutputWarningsToStandardError(False) self._qml_engine.warnings.connect(self.__onQmlWarning) for path in self._qml_import_paths: self._qml_engine.addImportPath(path) if not hasattr(sys, "frozen"): self._qml_engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._qml_engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._qml_engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._qml_engine) Bindings.register() self._qml_engine.load(self._main_qml) self.engineCreatedSignal.emit() recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self) -> List[QUrl]: return self._recent_files def _onJobFinished(self, job: Job) -> None: if isinstance(job, WriteFileJob): if not job.getResult() or not job.getAddToRecentFiles(): # For a write file job, if it failed or it doesn't need to be added to the recent files list, we do not # add it. return elif (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return if isinstance(job, (ReadMeshJob, ReadFileJob, WriteFileJob)): self.addFileToRecentFiles(job.getFileName()) def addFileToRecentFiles(self, file_name: str) -> None: file_path = QUrl.fromLocalFile(file_name) if file_path in self._recent_files: self._recent_files.remove(file_path) self._recent_files.insert(0, file_path) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" self.getPreferences().setValue("%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self) -> None: super().run() def hideMessage(self, message: Message) -> None: with self._message_lock: if message in self._visible_messages: message.hide(send_signal = False) # we're in handling hideMessageSignal so we don't want to resend it self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message: Message) -> None: with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._app_name, message.getText()) def _onMainWindowStateChanged(self, window_state: int) -> None: if self._tray_icon and self._tray_icon_widget: visible = window_state == Qt.WindowMinimized self._tray_icon_widget.setVisible(visible) # Show toast message using System tray widget. def showToastMessage(self, title: str, message: str) -> None: if self.checkWindowMinimizedState() and self._tray_icon_widget: # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does. # We should use the custom icon when we switch to Qt 5.9 self._tray_icon_widget.showMessage(title, message) def setMainQml(self, path: str) -> None: self._main_qml = path def exec_(self, *args: Any, **kwargs: Any) -> None: self.applicationRunning.emit() super().exec_(*args, **kwargs) @pyqtSlot() def reloadQML(self) -> None: # only reload when it is a release build if not self.getIsDebugMode(): return if self._qml_engine and self._theme: self._qml_engine.clearComponentCache() self._theme.reload() self._qml_engine.load(self._main_qml) # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted. for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.hide() @pyqtSlot() def purgeWindows(self) -> None: # Close all root objects except the last one. # Should only be called by onComponentCompleted of the mainWindow. if self._qml_engine: for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.close() @pyqtSlot("QList<QQmlError>") def __onQmlWarning(self, warnings: List[QQmlError]) -> None: for warning in warnings: Logger.log("w", warning.toString()) engineCreatedSignal = Signal() def isShuttingDown(self) -> bool: return self._is_shutting_down def registerObjects(self, engine) -> None: #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere. engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self) -> QtRenderer: if not self._renderer: self._renderer = QtRenderer() return cast(QtRenderer, self._renderer) mainWindowChanged = Signal() def getMainWindow(self) -> Optional[MainWindow]: return self._main_window def setMainWindow(self, window: MainWindow) -> None: if window != self._main_window: if self._main_window is not None: self._main_window.windowStateChanged.disconnect(self._onMainWindowStateChanged) self._main_window = window if self._main_window is not None: self._main_window.windowStateChanged.connect(self._onMainWindowStateChanged) self.mainWindowChanged.emit() def setVisible(self, visible: bool) -> None: if self._main_window is not None: self._main_window.visible = visible @property def isVisible(self) -> bool: if self._main_window is not None: return self._main_window.visible #type: ignore #MyPy doesn't realise that self._main_window cannot be None here. return False def getTheme(self) -> Optional[Theme]: if self._theme is None: if self._qml_engine is None: Logger.log("e", "The theme cannot be accessed before the engine is initialised") return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._qml_engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event: QEvent) -> None: e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event: QEvent) -> bool: if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self, save_data: bool = True) -> None: Logger.log("d", "Shutting down %s", self.getApplicationName()) self._is_shutting_down = True # garbage collect tray icon so it gets properly closed before the application is closed self._tray_icon_widget = None if save_data: try: self.savePreferences() except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) if self._tray_icon_widget: self._tray_icon_widget.deleteLater() self.quit() def checkWindowMinimizedState(self) -> bool: if self._main_window is not None and self._main_window.windowState() == Qt.WindowMinimized: return True else: return False ## Get the backend of the application (the program that does the heavy lifting). # The backend is also a QObject, which can be used from qml. @pyqtSlot(result = "QObject*") def getBackend(self) -> Backend: return self._backend ## Property used to expose the backend # It is made static as the backend is not supposed to change during runtime. # This makes the connection between backend and QML more reliable than the pyqtSlot above. # \returns Backend \type{Backend} @pyqtProperty("QVariant", constant = True) def backend(self) -> Backend: return self.getBackend() ## Create a class variable so we can manage the splash in the CrashHandler dialog when the Application instance # is not yet created, e.g. when an error occurs during the initialization splash = None # type: Optional[QSplashScreen] def createSplash(self) -> None: if not self.getIsHeadLess(): try: QtApplication.splash = self._createSplashScreen() except FileNotFoundError: QtApplication.splash = None else: if QtApplication.splash: QtApplication.splash.show() self.processEvents() ## Display text on the splash screen. def showSplashMessage(self, message: str) -> None: if not QtApplication.splash: self.createSplash() if QtApplication.splash: QtApplication.splash.showMessage(message, Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() elif self.getIsHeadLess(): Logger.log("d", message) ## Close the splash screen after the application has started. def closeSplash(self) -> None: if QtApplication.splash: QtApplication.splash.close() QtApplication.splash = None ## Create a QML component from a qml file. # \param qml_file_path: The absolute file path to the root qml file. # \param context_properties: Optional dictionary containing the properties that will be set on the context of the # qml instance before creation. # \return None in case the creation failed (qml error), else it returns the qml instance. # \note If the creation fails, this function will ensure any errors are logged to the logging service. def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]: if self._qml_engine is None: # Protect in case the engine was not initialized yet return None path = QUrl.fromLocalFile(qml_file_path) component = QQmlComponent(self._qml_engine, path) result_context = QQmlContext(self._qml_engine.rootContext()) #type: ignore #MyPy doens't realise that self._qml_engine can't be None here. if context_properties is not None: for name, value in context_properties.items(): result_context.setContextProperty(name, value) result = component.create(result_context) for err in component.errors(): Logger.log("e", str(err.toString())) if result is None: return None # We need to store the context with the qml object, else the context gets garbage collected and the qml objects # no longer function correctly/application crashes. result.attached_context = result_context return result ## Delete all nodes containing mesh data in the scene. # \param only_selectable. Set this to False to delete objects from all build plates @pyqtSlot() def deleteAll(self, only_selectable = True) -> None: Logger.log("i", "Clearing scene") if not self.getController().getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if only_selectable and not node.isSelectable(): continue if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"): continue # Only remove nodes that are selectable. if node.getParent() and cast(SceneNode, node.getParent()).callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getController().getScene().sceneChanged.emit(node) op.push() Selection.clear() ## Get the MeshFileHandler of this application. def getMeshFileHandler(self) -> MeshFileHandler: return self._mesh_file_handler def getWorkspaceFileHandler(self) -> WorkspaceFileHandler: return self._workspace_file_handler @pyqtSlot(result = QObject) def getPackageManager(self) -> PackageManager: return self._package_manager ## Gets the instance of this application. # # This is just to further specify the type of Application.getInstance(). # \return The instance of this application. @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": return cast(QtApplication, super().getInstance(**kwargs)) def _createSplashScreen(self) -> QSplashScreen: return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self) -> float: # OSX handles sizes of dialogs behind our backs, but other platforms need # to know about the device pixel ratio if sys.platform == "darwin": return 1.0 else: # determine a device pixel ratio from font metrics, using the same logic as UM.Theme fontPixelRatio = QFontMetrics(QCoreApplication.instance().font()).ascent() / 11 # round the font pixel ratio to quarters fontPixelRatio = int(fontPixelRatio * 4) / 4 return fontPixelRatio
import sys import os from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import QUrl from PyQt5.QtQml import QQmlComponent, QQmlApplicationEngine from PyQt5.QtQuick import QQuickWindow from view_model import ViewModel if __name__ == '__main__': myApp = QApplication(sys.argv) engine = QQmlApplicationEngine() context = engine.rootContext() engine.addImportPath("/home/bob/Qt/5.11.2/Automotive/sources/qtapplicationmanager/dummyimports/") # create a view model view_model = ViewModel() # bind the view model to the context context.setContextProperty('view_model', view_model) component = QQmlComponent(engine) component.loadUrl(QUrl('mainwindow.qml')) # some boilerplate to make sure the component is ready before showing if component.status() != QQmlComponent.Ready: if component.status() == QQmlComponent.Error: sys.exit(component.errorString())
class QtApplication(QApplication, Application): def __init__(self, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion() if major_version is None and minor_version is None and profile is None: Logger.log("e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting") QMessageBox.critical(None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers.") sys.exit(1) else: Logger.log("d", "Detected most suitable OpenGL context version: %s" % ( OpenGLContext.versionAsText(major_version, minor_version, profile))) OpenGLContext.setDefaultFormat(major_version, minor_version, profile = profile) self._plugins_loaded = False # Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._theme = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(Application.getInstallPrefix(), "Resources", "qml")) try: self._splash = self._createSplashScreen() except FileNotFoundError: self._splash = None else: self._splash.show() self.processEvents() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().upgrade() if upgraded: # Preferences might have changed. Load them again. # Note that the language can't be updated, so that will always revert to English. preferences = Preferences.getInstance() try: preferences.readFromFile(Resources.getPath(Resources.Preferences, self._application_name + ".cfg")) except FileNotFoundError: pass self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file) except FileNotFoundError: pass self.getApplicationName() Preferences.getInstance().addPreference("%s/recent_files" % self.getApplicationName(), "") self._recent_files = [] files = Preferences.getInstance().getValue("%s/recent_files" % self.getApplicationName()).split(";") for f in files: if not os.path.isfile(f): continue self._recent_files.append(QUrl.fromLocalFile(f)) JobQueue.getInstance().jobFinished.connect(self._onJobFinished) recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self): return self._recent_files def _onJobFinished(self, job): if (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return f = QUrl.fromLocalFile(job.getFileName()) if f in self._recent_files: self._recent_files.remove(f) self._recent_files.insert(0, f) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" Preferences.getInstance().setValue("%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self): pass def hideMessage(self, message): with self._message_lock: if message in self._visible_messages: self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message): with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setTimer(QTimer()) self.visibleMessageAdded.emit(message) def setMainQml(self, path): self._main_qml = path def initializeEngine(self): # TODO: Document native/qml import trickery Bindings.register() self._engine = QQmlApplicationEngine() for path in self._qml_import_paths: self._engine.addImportPath(path) if not hasattr(sys, "frozen"): self._engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._engine) self._engine.load(self._main_qml) self.engineCreatedSignal.emit() engineCreatedSignal = Signal() def isShuttingDown(self): return self._shutting_down def registerObjects(self, engine): engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self): if not self._renderer: self._renderer = QtRenderer() return self._renderer @classmethod def addCommandLineOptions(self, parser): super().addCommandLineOptions(parser) parser.add_argument("--disable-textures", dest="disable-textures", action="store_true", default=False, help="Disable Qt texture loading as a workaround for certain crashes.") parser.add_argument("-qmljsdebugger", help="For Qt's QML debugger compatibility") mainWindowChanged = Signal() def getMainWindow(self): return self._main_window def setMainWindow(self, window): if window != self._main_window: self._main_window = window self.mainWindowChanged.emit() def getTheme(self, *args): if self._theme is None: if self._engine is None: Logger.log("e", "The theme cannot be accessed before the engine is initialised") return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event): e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event): if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self): Logger.log("d", "Shutting down %s", self.getApplicationName()) self._shutting_down = True try: Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, self.getApplicationName() + ".cfg")) except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) self.quit() ## Get the backend of the application (the program that does the heavy lifting). # The backend is also a QObject, which can be used from qml. # \returns Backend \type{Backend} @pyqtSlot(result="QObject*") def getBackend(self): return self._backend ## Load a Qt translation catalog. # # This method will locate, load and install a Qt message catalog that can be used # by Qt's translation system, like qsTr() in QML files. # # \param file The file name to load, without extension. It will be searched for in # the i18nLocation Resources directory. If it can not be found a warning # will be logged but no error will be thrown. # \param language The language to load translations for. This can be any valid language code # or 'default' in which case the language is looked up based on system locale. # If the specified language can not be found, this method will fall back to # loading the english translations file. # # \note When `language` is `default`, the language to load can be changed with the # environment variable "LANGUAGE". def loadQtTranslation(self, file, language = "default"): # TODO Add support for specifying a language from preferences path = None if language == "default": path = self._getDefaultLanguage(file) else: path = Resources.getPath(Resources.i18n, language, "LC_MESSAGES", file + ".qm") # If all else fails, fall back to english. if not path: Logger.log("w", "Could not find any translations matching {0} for file {1}, falling back to english".format(language, file)) try: path = Resources.getPath(Resources.i18n, "en", "LC_MESSAGES", file + ".qm") except FileNotFoundError: Logger.log("w", "Could not find English translations for file {0}. Switching to developer english.".format(file)) return translator = QTranslator() if not translator.load(path): Logger.log("e", "Unable to load translations %s", file) return # Store a reference to the translator. # This prevents the translator from being destroyed before Qt has a chance to use it. self._translators[file] = translator # Finally, install the translator so Qt can use it. self.installTranslator(translator) ## Display text on the splash screen. def showSplashMessage(self, message): if self._splash: self._splash.showMessage(message , Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() ## Close the splash screen after the application has started. def closeSplash(self): if self._splash: self._splash.close() self._splash = None def _createSplashScreen(self): return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self): physical_dpi = QGuiApplication.primaryScreen().physicalDotsPerInch() # Typically 'normal' screens have a DPI around 96. Modern high DPI screens are up around 220. # We scale the low DPI screens with a traditional 1, and double the high DPI ones. return 1.0 if physical_dpi < 150 else 2.0 def _getDefaultLanguage(self, file): # If we have a language override set in the environment, try and use that. lang = os.getenv("URANIUM_LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # Else, try and get the current language from preferences lang = Preferences.getInstance().getValue("general/language") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If none of those are set, try to use the environment's LANGUAGE variable. lang = os.getenv("LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If looking up the language from the enviroment or preferences fails, try and use Qt's system locale instead. locale = QLocale.system() # First, try and find a directory for any of the provided languages for lang in locale.uiLanguages(): try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If that fails, see if we can extract a language "class" from the # preferred language. This will turn "en-GB" into "en" for example. lang = locale.uiLanguages()[0] lang = lang[0:lang.find("-")] try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass return None
class UiState(QObject): sigComPortOpened = pyqtSignal() sigComPortClosed = pyqtSignal() sigPowerOn = pyqtSignal() sigPowerOff = pyqtSignal() def __init__(self): super().__init__() self.fsm = QStateMachine() self.qmlEngine = QQmlApplicationEngine() self.qmlEngine.addImportPath("qml") self.qmlEngine.addImportPath("lib") self.qmlEngine.load(QUrl('qrc:/qml/main.qml')) self.rootObject = self.qmlEngine.rootObjects()[0] self.rootObject.comPortOpened.connect(self.sigComPortOpened) self.rootObject.comPortClosed.connect(self.sigComPortClosed) self.rootObject.powerOn.connect(self.sigPowerOn) self.rootObject.powerOff.connect(self.sigPowerOff) self.createState() pass def createState(self): # state defintion mainState = QState(self.fsm) finalState = QFinalState(self.fsm) self.fsm.setInitialState(mainState) initState = QState(mainState) openState = QState(mainState) mainState.setInitialState(initState) standbyState = QState(openState) processingState = QState(openState) openState.setInitialState(standbyState) # transition defition initState.addTransition(self.sigComPortOpened, openState) openState.addTransition(self.sigComPortClosed, initState) standbyState.addTransition(self.sigPowerOn, processingState) processingState.addTransition(self.sigPowerOff, standbyState) initState.entered.connect(self.initStateEntered) openState.entered.connect(self.openStateEntered) standbyState.entered.connect(self.standbyStateEntered) processingState.entered.connect(self.processingStateEntered) # fsm start self.fsm.start() pass @pyqtSlot() def initStateEntered(self): print("init") QMetaObject.invokeMethod(self.rootObject, "setPathViewIndex", QtCore.Q_ARG("QVariant", 0)) pass @pyqtSlot() def openStateEntered(self): print("open") pass @pyqtSlot() def standbyStateEntered(self): print("standby") QMetaObject.invokeMethod(self.rootObject, "setPathViewIndex", QtCore.Q_ARG("QVariant", 1)) pass @pyqtSlot() def processingStateEntered(self): print("processing") QMetaObject.invokeMethod(self.rootObject, "setPathViewIndex",QtCore.Q_ARG("QVariant", 2)) pass
class QtApplication(QApplication, Application): pluginsLoaded = Signal() applicationRunning = Signal() def __init__(self, tray_icon_name: str = None, **kwargs) -> None: plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(self.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) # use Qt Quick Scene Graph "basic" render loop os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) # type: ignore self._qml_import_paths = [] #type: List[str] self._main_qml = "main.qml" #type: str self._qml_engine = None #type: Optional[QQmlApplicationEngine] self._main_window = None #type: Optional[MainWindow] self._tray_icon_name = tray_icon_name #type: Optional[str] self._tray_icon = None #type: Optional[str] self._tray_icon_widget = None #type: Optional[QSystemTrayIcon] self._theme = None #type: Optional[Theme] self._renderer = None #type: Optional[QtRenderer] self._job_queue = None #type: Optional[JobQueue] self._version_upgrade_manager = None #type: Optional[VersionUpgradeManager] self._is_shutting_down = False #type: bool self._recent_files = [] #type: List[QUrl] self._configuration_error_message = None #type: Optional[ConfigurationErrorMessage] def addCommandLineOptions(self) -> None: super().addCommandLineOptions() # This flag is used by QApplication. We don't process it. self._cli_parser.add_argument("-qmljsdebugger", help = "For Qt's QML debugger compatibility") def initialize(self) -> None: super().initialize() self._mesh_file_handler = MeshFileHandler(self) #type: MeshFileHandler self._workspace_file_handler = WorkspaceFileHandler(self) #type: WorkspaceFileHandler # For some reason, with Qt 5.9 and up, the default "windows" style seems like Windows 95. We have to set the # style to "fusion" so it looks less ugly. pyqt_version_parts = [int(n) for n in PYQT_VERSION_STR.split(".")] if len(pyqt_version_parts) < 2: # Make sure there are at less 2 parts in the version pyqt_version_parts += [0 for _ in range(2 - len(pyqt_version_parts))] if pyqt_version_parts[0] == 5 and pyqt_version_parts[1] > 8: self.setStyle("fusion") # For some reason, with Qt 5.9 and up, the default "windows" style seems like Windows 95. We have to set the # style to "fusion" so it looks less ugly. pyqt_version_parts = [int(n) for n in PYQT_VERSION_STR.split(".")] if len(pyqt_version_parts) < 2: # Make sure there are at less 2 parts in the version pyqt_version_parts += [0 for _ in range(2 - len(pyqt_version_parts))] if pyqt_version_parts[0] == 5 and pyqt_version_parts[1] > 8: self.setStyle("fusion") self.setAttribute(Qt.AA_UseDesktopOpenGL) major_version, minor_version, profile = OpenGLContext.detectBestOpenGLVersion() if major_version is None and minor_version is None and profile is None: Logger.log("e", "Startup failed because OpenGL version probing has failed: tried to create a 2.0 and 4.1 context. Exiting") QMessageBox.critical(None, "Failed to probe OpenGL", "Could not probe OpenGL. This program requires OpenGL 2.0 or higher. Please check your video card drivers.") sys.exit(1) else: opengl_version_str = OpenGLContext.versionAsText(major_version, minor_version, profile) Logger.log("d", "Detected most suitable OpenGL context version: %s", opengl_version_str) OpenGLContext.setDefaultFormat(major_version, minor_version, profile = profile) self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(self.getInstallPrefix(), "Resources", "qml")) Logger.log("i", "Initializing job queue ...") self._job_queue = JobQueue() self._job_queue.jobFinished.connect(self._onJobFinished) Logger.log("i", "Initializing version upgrade manager ...") self._version_upgrade_manager = VersionUpgradeManager(self) def startSplashWindowPhase(self) -> None: super().startSplashWindowPhase() self._package_manager.initialize() # Read preferences here (upgrade won't work) to get the language in use, so the splash window can be shown in # the correct language. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(preferences_filename) except FileNotFoundError: Logger.log("i", "Preferences file not found, ignore and use default language '%s'", self._default_language) signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self._configuration_error_message = ConfigurationErrorMessage(self, i18n_catalog.i18nc("@info:status", "Your configuration seems to be corrupt."), lifetime = 0, title = i18n_catalog.i18nc("@info:title", "Configuration errors") ) # Remove, install, and then loading plugins self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) # Remove and install the plugins that have been scheduled self._plugin_registry.initializeBeforePluginsAreLoaded() self._loadPlugins() self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.pluginsLoaded.emit() self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) with self._container_registry.lockFile(): VersionUpgradeManager.getInstance().upgrade() # Load preferences again because before we have loaded the plugins, we don't have the upgrade routine for # the preferences file. Now that we have, load the preferences file again so it can be upgraded and loaded. try: preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") with open(preferences_filename, "r", encoding = "utf-8") as f: serialized = f.read() # This performs the upgrade for Preferences self._preferences.deserialize(serialized) self._preferences.setValue("general/plugins_to_remove", "") self._preferences.writeToFile(preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file cannot be found, will use default values") # Force the configuration file to be written again since the list of plugins to remove maybe changed self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: self._preferences_filename = Resources.getPath(Resources.Preferences, self._app_name + ".cfg") self._preferences.readFromFile(self._preferences_filename) except FileNotFoundError: Logger.log("i", "The preferences file '%s' cannot be found, will use default values", self._preferences_filename) self._preferences_filename = Resources.getStoragePath(Resources.Preferences, self._app_name + ".cfg") # FIXME: This is done here because we now use "plugins.json" to manage plugins instead of the Preferences file, # but the PluginRegistry will still import data from the Preferences files if present, such as disabled plugins, # so we need to reset those values AFTER the Preferences file is loaded. self._plugin_registry.initializeAfterPluginsAreLoaded() # Preferences: recent files self._preferences.addPreference("%s/recent_files" % self._app_name, "") file_names = self._preferences.getValue("%s/recent_files" % self._app_name).split(";") for file_name in file_names: if not os.path.isfile(file_name): continue self._recent_files.append(QUrl.fromLocalFile(file_name)) # Initialize System tray icon and make it invisible because it is used only to show pop up messages self._tray_icon = None if self._tray_icon_name: self._tray_icon = QIcon(Resources.getPath(Resources.Images, self._tray_icon_name)) self._tray_icon_widget = QSystemTrayIcon(self._tray_icon) self._tray_icon_widget.setVisible(False) def initializeEngine(self) -> None: # TODO: Document native/qml import trickery self._qml_engine = QQmlApplicationEngine(self) self._qml_engine.setOutputWarningsToStandardError(False) self._qml_engine.warnings.connect(self.__onQmlWarning) for path in self._qml_import_paths: self._qml_engine.addImportPath(path) if not hasattr(sys, "frozen"): self._qml_engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._qml_engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._qml_engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._qml_engine) Bindings.register() self._qml_engine.load(self._main_qml) self.engineCreatedSignal.emit() recentFilesChanged = pyqtSignal() @pyqtProperty("QVariantList", notify=recentFilesChanged) def recentFiles(self) -> List[QUrl]: return self._recent_files def _onJobFinished(self, job: Job) -> None: if (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult(): return f = QUrl.fromLocalFile(job.getFileName()) if f in self._recent_files: self._recent_files.remove(f) self._recent_files.insert(0, f) if len(self._recent_files) > 10: del self._recent_files[10] pref = "" for path in self._recent_files: pref += path.toLocalFile() + ";" self.getPreferences().setValue("%s/recent_files" % self.getApplicationName(), pref) self.recentFilesChanged.emit() def run(self) -> None: super().run() def hideMessage(self, message: Message) -> None: with self._message_lock: if message in self._visible_messages: message.hide(send_signal = False) # we're in handling hideMessageSignal so we don't want to resend it self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message: Message) -> None: with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setLifetimeTimer(QTimer()) message.setInactivityTimer(QTimer()) self.visibleMessageAdded.emit(message) # also show toast message when the main window is minimized self.showToastMessage(self._app_name, message.getText()) def _onMainWindowStateChanged(self, window_state: int) -> None: if self._tray_icon and self._tray_icon_widget: visible = window_state == Qt.WindowMinimized self._tray_icon_widget.setVisible(visible) # Show toast message using System tray widget. def showToastMessage(self, title: str, message: str) -> None: if self.checkWindowMinimizedState() and self._tray_icon_widget: # NOTE: Qt 5.8 don't support custom icon for the system tray messages, but Qt 5.9 does. # We should use the custom icon when we switch to Qt 5.9 self._tray_icon_widget.showMessage(title, message) def setMainQml(self, path: str) -> None: self._main_qml = path def exec_(self, *args: Any, **kwargs: Any) -> None: self.applicationRunning.emit() super().exec_(*args, **kwargs) @pyqtSlot() def reloadQML(self) -> None: # only reload when it is a release build if not self.getIsDebugMode(): return if self._qml_engine and self._theme: self._qml_engine.clearComponentCache() self._theme.reload() self._qml_engine.load(self._main_qml) # Hide the window. For some reason we can't close it yet. This needs to be done in the onComponentCompleted. for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.hide() @pyqtSlot() def purgeWindows(self) -> None: # Close all root objects except the last one. # Should only be called by onComponentCompleted of the mainWindow. if self._qml_engine: for obj in self._qml_engine.rootObjects(): if obj != self._qml_engine.rootObjects()[-1]: obj.close() @pyqtSlot("QList<QQmlError>") def __onQmlWarning(self, warnings: List[QQmlError]) -> None: for warning in warnings: Logger.log("w", warning.toString()) engineCreatedSignal = Signal() def isShuttingDown(self) -> bool: return self._is_shutting_down def registerObjects(self, engine) -> None: #type: ignore #Don't type engine, because the type depends on the platform you're running on so it always gives an error somewhere. engine.rootContext().setContextProperty("PluginRegistry", PluginRegistry.getInstance()) def getRenderer(self) -> QtRenderer: if not self._renderer: self._renderer = QtRenderer() return cast(QtRenderer, self._renderer) mainWindowChanged = Signal() def getMainWindow(self) -> Optional[MainWindow]: return self._main_window def setMainWindow(self, window: MainWindow) -> None: if window != self._main_window: if self._main_window is not None: self._main_window.windowStateChanged.disconnect(self._onMainWindowStateChanged) self._main_window = window if self._main_window is not None: self._main_window.windowStateChanged.connect(self._onMainWindowStateChanged) self.mainWindowChanged.emit() def setVisible(self, visible: bool) -> None: if self._main_window is not None: self._main_window.visible = visible @property def isVisible(self) -> bool: if self._main_window is not None: return self._main_window.visible #type: ignore #MyPy doesn't realise that self._main_window cannot be None here. return False def getTheme(self) -> Optional[Theme]: if self._theme is None: if self._qml_engine is None: Logger.log("e", "The theme cannot be accessed before the engine is initialised") return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._qml_engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event: QEvent) -> None: e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event: QEvent) -> bool: if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self, save_data: bool = True) -> None: Logger.log("d", "Shutting down %s", self.getApplicationName()) self._is_shutting_down = True if save_data: try: self.savePreferences() except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) self.quit() def checkWindowMinimizedState(self) -> bool: if self._main_window is not None and self._main_window.windowState() == Qt.WindowMinimized: return True else: return False ## Get the backend of the application (the program that does the heavy lifting). # The backend is also a QObject, which can be used from qml. @pyqtSlot(result = "QObject*") def getBackend(self) -> Backend: return self._backend ## Property used to expose the backend # It is made static as the backend is not supposed to change during runtime. # This makes the connection between backend and QML more reliable than the pyqtSlot above. # \returns Backend \type{Backend} @pyqtProperty("QVariant", constant = True) def backend(self) -> Backend: return self.getBackend() ## Create a class variable so we can manage the splash in the CrashHandler dialog when the Application instance # is not yet created, e.g. when an error occurs during the initialization splash = None def createSplash(self) -> None: if not self.getIsHeadLess(): try: QtApplication.splash = self._createSplashScreen() except FileNotFoundError: QtApplication.splash = None else: if QtApplication.splash: QtApplication.splash.show() self.processEvents() ## Display text on the splash screen. def showSplashMessage(self, message: str) -> None: if not QtApplication.splash: self.createSplash() if QtApplication.splash: QtApplication.splash.showMessage(message, Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() elif self.getIsHeadLess(): Logger.log("d", message) ## Close the splash screen after the application has started. def closeSplash(self) -> None: if QtApplication.splash: QtApplication.splash.close() QtApplication.splash = None ## Create a QML component from a qml file. # \param qml_file_path: The absolute file path to the root qml file. # \param context_properties: Optional dictionary containing the properties that will be set on the context of the # qml instance before creation. # \return None in case the creation failed (qml error), else it returns the qml instance. # \note If the creation fails, this function will ensure any errors are logged to the logging service. def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]: if self._qml_engine is None: # Protect in case the engine was not initialized yet return None path = QUrl.fromLocalFile(qml_file_path) component = QQmlComponent(self._qml_engine, path) result_context = QQmlContext(self._qml_engine.rootContext()) #type: ignore #MyPy doens't realise that self._qml_engine can't be None here. if context_properties is not None: for name, value in context_properties.items(): result_context.setContextProperty(name, value) result = component.create(result_context) for err in component.errors(): Logger.log("e", str(err.toString())) if result is None: return None # We need to store the context with the qml object, else the context gets garbage collected and the qml objects # no longer function correctly/application crashes. result.attached_context = result_context return result ## Delete all nodes containing mesh data in the scene. # \param only_selectable. Set this to False to delete objects from all build plates @pyqtSlot() def deleteAll(self, only_selectable = True) -> None: Logger.log("i", "Clearing scene") if not self.getController().getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if only_selectable and not node.isSelectable(): continue if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"): continue # Only remove nodes that are selectable. if node.getParent() and cast(SceneNode, node.getParent()).callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getController().getScene().sceneChanged.emit(node) op.push() Selection.clear() ## Get the MeshFileHandler of this application. def getMeshFileHandler(self) -> MeshFileHandler: return self._mesh_file_handler def getWorkspaceFileHandler(self) -> WorkspaceFileHandler: return self._workspace_file_handler @pyqtSlot(result = QObject) def getPackageManager(self) -> PackageManager: return self._package_manager ## Gets the instance of this application. # # This is just to further specify the type of Application.getInstance(). # \return The instance of this application. @classmethod def getInstance(cls, *args, **kwargs) -> "QtApplication": return cast(QtApplication, super().getInstance(**kwargs)) def _createSplashScreen(self) -> QSplashScreen: return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self) -> float: # OSX handles sizes of dialogs behind our backs, but other platforms need # to know about the device pixel ratio if sys.platform == "darwin": return 1.0 else: # determine a device pixel ratio from font metrics, using the same logic as UM.Theme fontPixelRatio = QFontMetrics(QCoreApplication.instance().font()).ascent() / 11 # round the font pixel ratio to quarters fontPixelRatio = int(fontPixelRatio * 4)/4 return fontPixelRatio
class QtApplication(QApplication, Application): def __init__(self, **kwargs): plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) else: import site for dir in site.getsitepackages(): QCoreApplication.addLibraryPath(os.path.join(dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(Application.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s" % (plugin_path)) QCoreApplication.addLibraryPath(plugin_path) os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) self._plugins_loaded = False #Used to determine when it's safe to use the plug-ins. self._main_qml = "main.qml" self._engine = None self._renderer = None self._main_window = None self._theme = None self._shutting_down = False self._qml_import_paths = [] self._qml_import_paths.append(os.path.join(os.path.dirname(sys.executable), "qml")) self._qml_import_paths.append(os.path.join(Application.getInstallPrefix(), "Resources", "qml")) self.setAttribute(Qt.AA_UseDesktopOpenGL) try: self._splash = self._createSplashScreen() except FileNotFoundError: self._splash = None else: self._splash.show() self.processEvents() signal.signal(signal.SIGINT, signal.SIG_DFL) # This is done here as a lot of plugins require a correct gl context. If you want to change the framework, # these checks need to be done in your <framework>Application.py class __init__(). i18n_catalog = i18nCatalog("uranium") self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading plugins...")) self._loadPlugins() self.parseCommandLine() Logger.log("i", "Command line arguments: %s", self._parsed_command_line) self._plugin_registry.checkRequiredPlugins(self.getRequiredPlugins()) self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Updating configuration...")) upgraded = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().upgrade() if upgraded: preferences = UM.Preferences.getInstance() #Preferences might have changed. Load them again. #Note that the language can't be updated, so that will always revert to English. try: preferences.readFromFile(Resources.getPath(Resources.Preferences, self._application_name + ".cfg")) except FileNotFoundError: pass self.showSplashMessage(i18n_catalog.i18nc("@info:progress", "Loading preferences...")) try: file = Resources.getPath(Resources.Preferences, self.getApplicationName() + ".cfg") Preferences.getInstance().readFromFile(file) except FileNotFoundError: pass def run(self): pass def hideMessage(self, message): with self._message_lock: if message in self._visible_messages: self._visible_messages.remove(message) self.visibleMessageRemoved.emit(message) def showMessage(self, message): with self._message_lock: if message not in self._visible_messages: self._visible_messages.append(message) message.setTimer(QTimer()) self.visibleMessageAdded.emit(message) def setMainQml(self, path): self._main_qml = path def initializeEngine(self): # TODO: Document native/qml import trickery Bindings.register() self._engine = QQmlApplicationEngine() for path in self._qml_import_paths: self._engine.addImportPath(path) if not hasattr(sys, "frozen"): self._engine.addImportPath(os.path.join(os.path.dirname(__file__), "qml")) self._engine.rootContext().setContextProperty("QT_VERSION_STR", QT_VERSION_STR) self._engine.rootContext().setContextProperty("screenScaleFactor", self._screenScaleFactor()) self.registerObjects(self._engine) self._engine.load(self._main_qml) self.engineCreatedSignal.emit() engineCreatedSignal = Signal() def isShuttingDown(self): return self._shutting_down def registerObjects(self, engine): pass def getRenderer(self): if not self._renderer: self._renderer = QtRenderer() return self._renderer @classmethod def addCommandLineOptions(self, parser): parser.add_argument("--disable-textures", dest="disable-textures", action="store_true", default=False, help="Disable Qt texture loading as a workaround for certain crashes.") # Overridden from QApplication::setApplicationName to call our internal setApplicationName def setApplicationName(self, name): Application.setApplicationName(self, name) mainWindowChanged = Signal() def getMainWindow(self): return self._main_window def setMainWindow(self, window): if window != self._main_window: self._main_window = window self.mainWindowChanged.emit() def getTheme(self, *args): if self._theme is None: if self._engine is None: Logger.log("e", "The theme cannot be accessed before the engine is initialised") return None self._theme = UM.Qt.Bindings.Theme.Theme.getInstance(self._engine) return self._theme # Handle a function that should be called later. def functionEvent(self, event): e = _QtFunctionEvent(event) QCoreApplication.postEvent(self, e) # Handle Qt events def event(self, event): if event.type() == _QtFunctionEvent.QtFunctionEvent: event._function_event.call() return True return super().event(event) def windowClosed(self): Logger.log("d", "Shutting down %s", self.getApplicationName()) self._shutting_down = True try: Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, self.getApplicationName() + ".cfg")) except Exception as e: Logger.log("e", "Exception while saving preferences: %s", repr(e)) try: self.applicationShuttingDown.emit() except Exception as e: Logger.log("e", "Exception while emitting shutdown signal: %s", repr(e)) try: self.getBackend().close() except Exception as e: Logger.log("e", "Exception while closing backend: %s", repr(e)) self.quit() ## Load a Qt translation catalog. # # This method will locate, load and install a Qt message catalog that can be used # by Qt's translation system, like qsTr() in QML files. # # \param file The file name to load, without extension. It will be searched for in # the i18nLocation Resources directory. If it can not be found a warning # will be logged but no error will be thrown. # \param language The language to load translations for. This can be any valid language code # or 'default' in which case the language is looked up based on system locale. # If the specified language can not be found, this method will fall back to # loading the english translations file. # # \note When `language` is `default`, the language to load can be changed with the # environment variable "LANGUAGE". def loadQtTranslation(self, file, language = "default"): #TODO Add support for specifying a language from preferences path = None if language == "default": path = self._getDefaultLanguage(file) else: path = Resources.getPath(Resources.i18n, language, "LC_MESSAGES", file + ".qm") # If all else fails, fall back to english. if not path: Logger.log("w", "Could not find any translations matching {0} for file {1}, falling back to english".format(language, file)) try: path = Resources.getPath(Resources.i18n, "en", "LC_MESSAGES", file + ".qm") except FileNotFoundError: Logger.log("w", "Could not find English translations for file {0}. Switching to developer english.".format(file)) return translator = QTranslator() if not translator.load(path): Logger.log("e", "Unable to load translations %s", file) return # Store a reference to the translator. # This prevents the translator from being destroyed before Qt has a chance to use it. self._translators[file] = translator # Finally, install the translator so Qt can use it. self.installTranslator(translator) ## Display text on the splash screen. def showSplashMessage(self, message): if self._splash: self._splash.showMessage(message , Qt.AlignHCenter | Qt.AlignVCenter) self.processEvents() ## Close the splash screen after the application has started. def closeSplash(self): if self._splash: self._splash.close() self._splash = None def _createSplashScreen(self): return QSplashScreen(QPixmap(Resources.getPath(Resources.Images, self.getApplicationName() + ".png"))) def _screenScaleFactor(self): physical_dpi = QGuiApplication.primaryScreen().physicalDotsPerInch() # Typically 'normal' screens have a DPI around 96. Modern high DPI screens are up around 220. # We scale the low DPI screens with a traditional 1, and double the high DPI ones. return 1.0 if physical_dpi < 150 else 2.0 def _getDefaultLanguage(self, file): # If we have a language override set in the environment, try and use that. lang = os.getenv("URANIUM_LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # Else, try and get the current language from preferences lang = Preferences.getInstance().getValue("general/language") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If none of those are set, try to use the environment's LANGUAGE variable. lang = os.getenv("LANGUAGE") if lang: try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If looking up the language from the enviroment or preferences fails, try and use Qt's system locale instead. locale = QLocale.system() # First, try and find a directory for any of the provided languages for lang in locale.uiLanguages(): try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass # If that fails, see if we can extract a language "class" from the # preferred language. This will turn "en-GB" into "en" for example. lang = locale.uiLanguages()[0] lang = lang[0:lang.find("-")] try: return Resources.getPath(Resources.i18n, lang, "LC_MESSAGES", file + ".qm") except FileNotFoundError: pass return None
import os from PyQt5.QtCore import QUrl, Qt, QTimer, QMetaObject, QObject from PyQt5.QtGui import QGuiApplication from PyQt5.QtQml import QQmlApplicationEngine from dataProc import GraphRefresh from tools import Tools if __name__ == '__main__': try: pid = str(os.getpid()) PIDFILE = '/tmp/ui_process.pid' pidfile = open(PIDFILE,'w') pidfile.write(pid) pidfile.close() cwd = sys.path[0] myApp = QGuiApplication(sys.argv) appLabel = QQmlApplicationEngine() appLabel.addImportPath(cwd) appLabel.addImportPath(cwd + '/qml') appLabel.load(cwd + '/splash.qml') root = appLabel.rootObjects() context = appLabel.rootContext() g = GraphRefresh("/dev/ttyUSB0", 115200, 1, root, context) tools = Tools(root, context, g) context.setContextProperty('pyRoot', tools) myApp.exec_() sys.exit() except Exception as e : print(e) ;
@description: """ import os import sys try: from PyQt5.QtCore import QCoreApplication, Qt, QUrl from PyQt5.QtQml import QQmlApplicationEngine from PyQt5.QtWidgets import QApplication, QMessageBox except ImportError: from PySide2.QtCore import QCoreApplication, Qt, QUrl from PySide2.QtQml import QQmlApplicationEngine from PySide2.QtWidgets import QApplication, QMessageBox if __name__ == '__main__': try: QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) except: pass os.chdir('FlatStyle') app = QApplication(sys.argv) engine = QQmlApplicationEngine() engine.objectCreated.connect(lambda obj, _: (QMessageBox.critical( None, '错误', '运行失败,可能是当前PyQt版本不支持'), engine.quit) if not obj else 0) engine.addImportPath('imports') engine.load(QUrl('flatstyle.qml')) sys.exit(app.exec_())