Sun, 15 Nov 2020 19:53:56 +0100
Started support for translations support.
--- a/PluginFlask.e4p Sun Nov 15 17:35:48 2020 +0100 +++ b/PluginFlask.e4p Sun Nov 15 19:53:56 2020 +0100 @@ -18,6 +18,7 @@ <Source>ProjectFlask/AnsiTools.py</Source> <Source>ProjectFlask/ConfigurationPage/FlaskPage.py</Source> <Source>ProjectFlask/ConfigurationPage/__init__.py</Source> + <Source>ProjectFlask/FlaskBabelDetector.py</Source> <Source>ProjectFlask/FlaskCommandDialog.py</Source> <Source>ProjectFlask/Project.py</Source> <Source>ProjectFlask/RoutesDialog.py</Source>
--- a/PluginProjectFlask.py Sun Nov 15 17:35:48 2020 +0100 +++ b/PluginProjectFlask.py Sun Nov 15 19:53:56 2020 +0100 @@ -135,6 +135,8 @@ "FlaskDocUrl": "https://flask.palletsprojects.com", + "TranslationsEditor": "", + "UseExternalBrowser": False, } if isWindowsPlatform(): @@ -199,7 +201,7 @@ "Flask", self.tr("Flask"), self.fileTypesCallback, lexerAssociationCallback=self.lexerAssociationCallback, -# binaryTranslationsCallback=self.binaryTranslationsCallback, + binaryTranslationsCallback=self.binaryTranslationsCallback, progLanguages=self.__supportedVariants[:]) from Project.ProjectBrowser import ( @@ -214,7 +216,7 @@ if self.__e5project.isOpen(): self.__projectOpened() -# self.__object.projectOpenedHooks() + self.__object.projectOpenedHooks() e5App().getObject("Project").projectOpened.connect( self.__projectOpened) @@ -222,13 +224,13 @@ self.__projectClosed) e5App().getObject("Project").newProject.connect( self.__projectOpened) -# -# e5App().getObject("Project").projectOpenedHooks.connect( -# self.__object.projectOpenedHooks) -# e5App().getObject("Project").projectClosedHooks.connect( -# self.__object.projectClosedHooks) -# e5App().getObject("Project").newProjectHooks.connect( -# self.__object.projectOpenedHooks) + + e5App().getObject("Project").projectOpenedHooks.connect( + self.__object.projectOpenedHooks) + e5App().getObject("Project").projectClosedHooks.connect( + self.__object.projectClosedHooks) + e5App().getObject("Project").newProjectHooks.connect( + self.__object.projectOpenedHooks) return None, True @@ -244,18 +246,18 @@ self.__projectClosed) e5App().getObject("Project").newProject.disconnect( self.__projectOpened) -# -# e5App().getObject("Project").projectOpenedHooks.disconnect( -# self.__object.projectOpenedHooks) -# e5App().getObject("Project").projectClosedHooks.disconnect( -# self.__object.projectClosedHooks) -# e5App().getObject("Project").newProjectHooks.disconnect( -# self.__object.projectOpenedHooks) + + e5App().getObject("Project").projectOpenedHooks.disconnect( + self.__object.projectOpenedHooks) + e5App().getObject("Project").projectClosedHooks.disconnect( + self.__object.projectClosedHooks) + e5App().getObject("Project").newProjectHooks.disconnect( + self.__object.projectOpenedHooks) self.__e5project.unregisterProjectType("Flask") -# self.__object.projectClosedHooks() -# self.__projectClosed() + self.__object.projectClosedHooks() + self.__projectClosed() self.__initialize() @@ -352,6 +354,21 @@ return "" + def binaryTranslationsCallback(self, filename): + """ + Public method to determine the filename of a compiled translation file + given the translation source file. + + @param filename name of the translation source file + @type str + @return name of the binary translation file + @rtype str + """ + if filename.endswith(".po"): + return filename.replace(".po", ".mo") + + return filename + def getDefaultPreference(self, key): """ Public method to get the default value for a setting.
--- a/ProjectFlask/ConfigurationPage/FlaskPage.py Sun Nov 15 17:35:48 2020 +0100 +++ b/ProjectFlask/ConfigurationPage/FlaskPage.py Sun Nov 15 19:53:56 2020 +0100 @@ -10,6 +10,7 @@ from PyQt5.QtCore import pyqtSlot from E5Gui.E5Application import e5App +from E5Gui.E5PathPicker import E5PathPickerModes from Preferences.ConfigurationPages.ConfigurationPageBase import ( ConfigurationPageBase @@ -65,6 +66,9 @@ self.py3VenvNameComboBox.addItems( [""] + sorted(venvManager.getVirtualenvNames())) + self.translationsEditorPicker.setMode(E5PathPickerModes.OpenFileMode) + self.translationsEditorPicker.setFilters(self.tr("All Files (*)")) + # set initial values self.consoleCommandCombo.setEditText( self.__plugin.getPreferences("ConsoleCommand")) @@ -86,6 +90,9 @@ self.urlEdit.setText( self.__plugin.getPreferences("FlaskDocUrl")) + + self.translationsEditorPicker.setText( + self.__plugin.getPreferences("TranslationsEditor")) def save(self): """ @@ -107,6 +114,9 @@ self.__plugin.setPreferences( "FlaskDocUrl", self.urlEdit.text()) + + self.__plugin.setPreferences( + "TranslationsEditor", self.translationsEditorPicker.text()) @pyqtSlot() def on_py3VenvNamesReloadButton_clicked(self):
--- a/ProjectFlask/ConfigurationPage/FlaskPage.ui Sun Nov 15 17:35:48 2020 +0100 +++ b/ProjectFlask/ConfigurationPage/FlaskPage.ui Sun Nov 15 19:53:56 2020 +0100 @@ -7,10 +7,10 @@ <x>0</x> <y>0</y> <width>500</width> - <height>508</height> + <height>740</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <widget class="QLabel" name="headerLabel"> <property name="text"> @@ -175,6 +175,47 @@ </widget> </item> <item> + <widget class="QGroupBox" name="TranslationsGroup"> + <property name="title"> + <string>Translations Editor</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_10"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>40</height> + </size> + </property> + <property name="text"> + <string>Enter the path of an editor to use to do the translations. Leave empty to disable this feature.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="E5PathPicker" name="translationsEditorPicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path of the translations editor</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -189,6 +230,14 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> <tabstops> <tabstop>consoleCommandCombo</tabstop> <tabstop>externalBrowserCheckBox</tabstop>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskBabelDetector.py Sun Nov 15 19:53:56 2020 +0100 @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module to check for the presence of 'flask-babel' by importing it. +""" + +import sys + +if __name__ == "__main__": + try: + import flask_babel # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + ret = 0 + except ImportError: + ret = 1 + + sys.exit(ret)
--- a/ProjectFlask/FlaskCommandDialog.py Sun Nov 15 17:35:48 2020 +0100 +++ b/ProjectFlask/FlaskCommandDialog.py Sun Nov 15 19:53:56 2020 +0100 @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + """ Module implementing a dialog to run a flask command and show its output. """ @@ -133,7 +136,7 @@ @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ - Slot handling presses of the button box buttons. + Private slot handling presses of the button box buttons. @param button reference to the button been clicked @type QAbstractButton
--- a/ProjectFlask/Project.py Sun Nov 15 17:35:48 2020 +0100 +++ b/ProjectFlask/Project.py Sun Nov 15 19:53:56 2020 +0100 @@ -53,6 +53,7 @@ self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") self.__menus = {} # dictionary with references to menus + self.__hooksInstalled = False self.__serverDialog = None self.__routesDialog = None @@ -264,6 +265,78 @@ """ return list(self.__menus.keys()) + def registerOpenHook(self): + """ + Public method to register the open hook to open a translations file + in a translations editor. + """ + if self.__hooksInstalled: + editor = self.__plugin.getPreferences("TranslationsEditor") + if editor: + self.__translationsBrowser.addHookMethodAndMenuEntry( + "open", self.openPOEditor, + self.tr("Open with {0}").format( + os.path.basename(editor))) + else: + self.__translationsBrowser.removeHookMethod("open") + + def projectOpenedHooks(self): + """ + Public method to add our hook methods. + """ + if self.__e5project.getProjectType() == "Flask": +## self.__formsBrowser = ( +## e5App().getObject("ProjectBrowser") +## .getProjectBrowser("forms")) +## self.__formsBrowser.addHookMethodAndMenuEntry( +## "newForm", self.newForm, self.tr("New template...")) +## + if self.flaskBabelAvailable(): + self.__e5project.projectLanguageAddedByCode.connect( + self.__projectLanguageAdded) + self.__translationsBrowser = ( + e5App().getObject("ProjectBrowser") + .getProjectBrowser("translations")) + self.__translationsBrowser.addHookMethodAndMenuEntry( + "extractMessages", self.extractMessages, + self.tr("Extract Messages")) + self.__translationsBrowser.addHookMethodAndMenuEntry( + "releaseAll", self.compileCatalogs, + self.tr("Compile All Catalogs")) + self.__translationsBrowser.addHookMethodAndMenuEntry( + "releaseSelected", self.compileSelectedCatalogs, + self.tr("Compile Selected Catalogs")) + self.__translationsBrowser.addHookMethodAndMenuEntry( + "generateAll", self.updateCatalogs, + self.tr("Update All Catalogs")) + self.__translationsBrowser.addHookMethodAndMenuEntry( + "generateSelected", self.updateSelectedCatalogs, + self.tr("Update Selected Catalogs")) + + self.__hooksInstalled = True + + self.registerOpenHook() + + def projectClosedHooks(self): + """ + Public method to remove our hook methods. + """ + if self.__hooksInstalled: +## self.__formsBrowser.removeHookMethod("newForm") +## self.__formsBrowser = None +## + self.__e5project.projectLanguageAddedByCode.disconnect( + self.__projectLanguageAdded) + self.__translationsBrowser.removeHookMethod("extractMessages") + self.__translationsBrowser.removeHookMethod("releaseAll") + self.__translationsBrowser.removeHookMethod("releaseSelected") + self.__translationsBrowser.removeHookMethod("generateAll") + self.__translationsBrowser.removeHookMethod("generateSelected") + self.__translationsBrowser.removeHookMethod("open") + self.__translationsBrowser = None + + self.__hooksInstalled = False + ################################################################## ## slots below implement general functionality ################################################################## @@ -275,8 +348,6 @@ for dlg in (self.__serverDialog, self.__routesDialog): if dlg is not None: dlg.close() -## if self.__serverProc is not None: -## self.__serverProcFinished() def supportedPythonVariants(self): """ @@ -583,3 +654,57 @@ dlg = FlaskCommandDialog(self) if dlg.startCommand("init-db"): dlg.exec() + + ################################################################## + ## slots and methods below implement i18n and l10n support + ################################################################## + + def flaskBabelAvailable(self): + """ + Public method to check, if the 'flask-babel' package is available. + + @return flag indicating the availability of 'flask-babel' + @rtype bool + """ + venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3") + interpreter = self.__virtualEnvManager.getVirtualenvInterpreter( + venvName) + if interpreter and Utilities.isinpath(interpreter): + detector = os.path.join( + os.path.dirname(__file__), "FlaskBabelDetector.py") + proc = QProcess() + proc.setProcessChannelMode(QProcess.MergedChannels) + proc.start(interpreter, [detector]) + finished = proc.waitForFinished(30000) + if finished and proc.exitCode() == 0: + return True + + return False + + def __projectLanguageAdded(self, code): + # TODO: implement this with pybabel ... + pass + + def openPOEditor(self): + # TODO: implement this with pybabel ... + pass + + def extractMessages(self): + # TODO: implement this with pybabel ... + pass + + def compileCatalogs(self): + # TODO: implement this with pybabel ... + pass + + def compileSelectedCatalogs(self): + # TODO: implement this with pybabel ... + pass + + def updateCatalogs(self): + # TODO: implement this with pybabel ... + pass + + def updateSelectedCatalogs(self): + # TODO: implement this with pybabel ... + pass
--- a/ProjectFlask/RunServerDialog.py Sun Nov 15 17:35:48 2020 +0100 +++ b/ProjectFlask/RunServerDialog.py Sun Nov 15 19:53:56 2020 +0100 @@ -53,9 +53,9 @@ self.__process = None self.__serverUrl = "" - self.__ansiRe = re.compile("(\\x1b\[\d+m)") + self.__ansiRe = re.compile(r"(\x1b\[\d+m)") - self.__urlRe = re.compile(r""" * Running on ([^(]+) \(.*""") + self.__urlRe = re.compile(r" \* Running on ([^(]+) \(.*") self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) @@ -99,13 +99,15 @@ self.__restartModeAct.setText( self.tr("Re-start Server (Development Mode)")) - def startServer(self, development=False, restart=False, - askForOptions=False): + def startServer(self, development=False, askForOptions=False): """ Public method to start the Flask server process. @param development flag indicating development mode @type bool + @param askForOptions flag indicating to ask for server start options + first + @type bool @return flag indicating success @rtype bool """ @@ -263,6 +265,7 @@ # step 2: start a new server self.startServer(development=not self.__serverOptions["development"]) + @pyqtSlot() def __restartServerWithOptions(self): """