Sat, 14 Nov 2020 19:56:06 +0100
Continued implementing various flask actions.
--- a/PluginFlask.e4p Fri Nov 13 19:51:28 2020 +0100 +++ b/PluginFlask.e4p Sat Nov 14 19:56:06 2020 +0100 @@ -18,16 +18,20 @@ <Source>ProjectFlask/AnsiTools.py</Source> <Source>ProjectFlask/ConfigurationPage/FlaskPage.py</Source> <Source>ProjectFlask/ConfigurationPage/__init__.py</Source> + <Source>ProjectFlask/FlaskCommandDialog.py</Source> <Source>ProjectFlask/Project.py</Source> <Source>ProjectFlask/RoutesDialog.py</Source> <Source>ProjectFlask/RunServerDialog.py</Source> + <Source>ProjectFlask/ServerStartOptionsDialog.py</Source> <Source>ProjectFlask/__init__.py</Source> <Source>__init__.py</Source> </Sources> <Forms> <Form>ProjectFlask/ConfigurationPage/FlaskPage.ui</Form> + <Form>ProjectFlask/FlaskCommandDialog.ui</Form> <Form>ProjectFlask/RoutesDialog.ui</Form> <Form>ProjectFlask/RunServerDialog.ui</Form> + <Form>ProjectFlask/ServerStartOptionsDialog.ui</Form> </Forms> <Others> <Other>.hgignore</Other>
--- a/PluginProjectFlask.py Fri Nov 13 19:51:28 2020 +0100 +++ b/PluginProjectFlask.py Sat Nov 14 19:56:06 2020 +0100 @@ -134,41 +134,18 @@ "VirtualEnvironmentNamePy3": "", "FlaskDocUrl": "https://flask.palletsprojects.com", -# "Python3ConsoleType": "ipython", -# -# "ServerAddress": "", -# -# "RecentNumberApps": 10, -# "UseIPv6": False, -# "UseThreading": True, -# -# "TranslationsEditor": "", -# "FuzzyTranslations": False, "UseExternalBrowser": False, -# -# "CheckDeployMode": False, -# -# "RecentNumberTestData": 10, -# "KeepTestDatabase": False, -# -# "RecentNumberDatabaseNames": 10, } if isWindowsPlatform(): self.__defaults["AnsiColorScheme"] = "Windows 10" + self.__defaults["ConsoleCommand"] = "cmd.exe /c" elif isMacPlatform(): self.__defaults["AnsiColorScheme"] = "xterm" + self.__defaults["ConsoleCommand"] = "xterm -e" else: self.__defaults["AnsiColorScheme"] = "Ubuntu" -# if isWindowsPlatform(): -# self.__defaults["ConsoleCommandNoClose"] = "cmd.exe /k" -# self.__defaults["ConsoleCommand"] = "cmd.exe /c" -# elif isMacPlatform(): -# self.__defaults["ConsoleCommandNoClose"] = "xterm -hold -e" -# self.__defaults["ConsoleCommand"] = "xterm -e" -# else: -# self.__defaults["ConsoleCommandNoClose"] = "konsole --noclose -e" -# self.__defaults["ConsoleCommand"] = "konsole -e" + self.__defaults["ConsoleCommand"] = "konsole -e" self.__translator = None self.__loadTranslator()
--- a/ProjectFlask/ConfigurationPage/FlaskPage.py Fri Nov 13 19:51:28 2020 +0100 +++ b/ProjectFlask/ConfigurationPage/FlaskPage.py Sat Nov 14 19:56:06 2020 +0100 @@ -18,10 +18,11 @@ import UI.PixmapCache +from Globals import isWindowsPlatform, isMacPlatform + from .. import AnsiTools -# TODO: add selection for the ANSI color scheme (see MicroPython) class FlaskPage(ConfigurationPageBase, Ui_FlaskPage): """ Class implementing the Flask configuration page. @@ -36,11 +37,25 @@ self.setupUi(self) self.setObjectName("FlaskPage") + self.__plugin = plugin + + consoleList = [] + if isWindowsPlatform(): + consoleList.append("cmd.exe /c") + elif isMacPlatform(): + consoleList.append("xterm -e") + consoleList.append("/opt/X11/bin/xterm -e") + else: + consoleList.append("konsole -e") + consoleList.append("gnome-terminal -e") + consoleList.append("mate-terminal -e") + consoleList.append("xfce4-terminal -e") + consoleList.append("xterm -e") + self.consoleCommandCombo.addItems(consoleList) + self.colorSchemeComboBox.addItems( sorted(AnsiTools.getAvailableColorSchemes())) - self.__plugin = plugin - self.urlResetButton.setIcon( UI.PixmapCache.getIcon("editUndo")) self.py3VenvNamesReloadButton.setIcon( @@ -51,6 +66,9 @@ [""] + sorted(venvManager.getVirtualenvNames())) # set initial values + self.consoleCommandCombo.setEditText( + self.__plugin.getPreferences("ConsoleCommand")) + self.externalBrowserCheckBox.setChecked( self.__plugin.getPreferences("UseExternalBrowser")) @@ -74,6 +92,9 @@ Public slot to save the Flask configuration. """ self.__plugin.setPreferences( + "ConsoleCommand", self.consoleCommandCombo.currentText()) + + self.__plugin.setPreferences( "UseExternalBrowser", self.externalBrowserCheckBox.isChecked()) self.__plugin.setPreferences(
--- a/ProjectFlask/ConfigurationPage/FlaskPage.ui Fri Nov 13 19:51:28 2020 +0100 +++ b/ProjectFlask/ConfigurationPage/FlaskPage.ui Sat Nov 14 19:56:06 2020 +0100 @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>500</width> - <height>373</height> + <height>508</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -32,6 +32,38 @@ </widget> </item> <item> + <widget class="QGroupBox" name="pyramidConsoleGroup"> + <property name="title"> + <string>Console Command</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Console Command:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="consoleCommandCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Enter the console command</string> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QGroupBox" name="flaskBrowserGroup"> <property name="title"> <string>Web-Browser</string> @@ -158,6 +190,7 @@ </layout> </widget> <tabstops> + <tabstop>consoleCommandCombo</tabstop> <tabstop>externalBrowserCheckBox</tabstop> <tabstop>py3VenvNameComboBox</tabstop> <tabstop>py3VenvNamesReloadButton</tabstop>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskCommandDialog.py Sat Nov 14 19:56:06 2020 +0100 @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +""" +Module implementing a dialog to run a flask command and show its output. +""" + +from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton + +from E5Gui import E5MessageBox + +from .Ui_FlaskCommandDialog import Ui_FlaskCommandDialog + + +class FlaskCommandDialog(QDialog, Ui_FlaskCommandDialog): + """ + Class implementing a dialog to run a flask command and show its output. + """ + def __init__(self, project, parent=None): + """ + Constructor + + @param project reference to the project object + @type Project + @param parent reference to the parent widget + @type QWidget + """ + super(FlaskCommandDialog, self).__init__(parent) + self.setupUi(self) + + self.__project = project + + self.__process = None + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + + def startCommand(self, command, args=None): + """ + Public method to start a flask command and show its output. + + @param command flask command to be run + @type str + @param args list of command line arguments for the command + @type list of str + @return flag indicating a successful start + @rtype bool + """ + workdir, env = self.__project.prepareRuntimeEnvironment() + if env is not None: + flaskCommand = self.__project.getFlaskCommand() + + self.__process = QProcess() + self.__process.setProcessEnvironment(env) + self.__process.setWorkingDirectory(workdir) + self.__process.setProcessChannelMode(QProcess.MergedChannels) + + self.__process.readyReadStandardOutput.connect(self.__readStdOut) + self.__process.finished.connect(self.__processFinished) + + self.outputEdit.clear() + + flaskArgs = [command] + if args: + flaskArgs += args + + self.__process.start(flaskCommand, flaskArgs) + ok = self.__process.waitForStarted(10000) + if not ok: + E5MessageBox.critical( + None, + self.tr("Execute Flask Command"), + self.tr("""The Flask process could not be started.""")) + else: + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setFocus( + Qt.OtherFocusReason) + else: + ok = False + + return ok + + def closeEvent(self, evt): + """ + Protected method handling the close event of the dialog. + + @param evt reference to the close event object + @type QCloseEvent + """ + self.__cancelProcess() + evt.accept() + + @pyqtSlot() + def __readStdOut(self): + """ + Private slot to add the server process output to the output pane. + """ + if self.__process is not None: + out = str(self.__process.readAllStandardOutput(), "utf-8") + self.outputEdit.insertPlainText(out) + + @pyqtSlot() + def __processFinished(self): + """ + Private slot handling the finishing of the server process. + """ + self.__cancelProcess() + + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Close).setFocus( + Qt.OtherFocusReason) + + @pyqtSlot() + def __cancelProcess(self): + """ + Private slot to terminate the current process. + """ + if ( + self.__process is not None and + self.__process.state() != QProcess.NotRunning + ): + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.__process = None + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Slot handling presses of the button box buttons. + + @param button reference to the button been clicked + @type QAbstractButton + """ + if button is self.buttonBox.button(QDialogButtonBox.Close): + self.close() + elif button is self.buttonBox.button(QDialogButtonBox.Cancel): + self.__cancelProcess()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskCommandDialog.ui Sat Nov 14 19:56:06 2020 +0100 @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FlaskCommandDialog</class> + <widget class="QDialog" name="FlaskCommandDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Flask Command</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Output</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPlainTextEdit" name="outputEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="lineWrapMode"> + <enum>QPlainTextEdit::NoWrap</enum> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>outputEdit</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>FlaskCommandDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>505</x> + <y>474</y> + </hint> + <hint type="destinationlabel"> + <x>593</x> + <y>419</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/ProjectFlask/Project.py Fri Nov 13 19:51:28 2020 +0100 +++ b/ProjectFlask/Project.py Sat Nov 14 19:56:06 2020 +0100 @@ -10,7 +10,7 @@ import os from PyQt5.QtCore import ( - pyqtSlot, QObject, QProcess, QProcessEnvironment + pyqtSlot, QObject, QProcess, QProcessEnvironment, QTimer ) from PyQt5.QtWidgets import QMenu @@ -25,6 +25,7 @@ from .RunServerDialog import RunServerDialog from .RoutesDialog import RoutesDialog +from .FlaskCommandDialog import FlaskCommandDialog class Project(QObject): @@ -53,10 +54,10 @@ self.__menus = {} # dictionary with references to menus -## self.__serverProc = None self.__serverDialog = None self.__routesDialog = None - + self.__shellProcess = None + self.__flaskVersions = { "python": "", "flask": "", @@ -101,9 +102,43 @@ self.runDevServerAct.triggered.connect(self.__runDevelopmentServer) self.actions.append(self.runDevServerAct) - ############################## + self.askForServerOptionsAct = E5Action( + self.tr('Ask for Server Start Options'), + self.tr('Ask for Server Start Options'), + 0, 0, + self, 'flask_ask_server_options') + self.askForServerOptionsAct.setStatusTip(self.tr( + 'Ask for server start options')) + self.askForServerOptionsAct.setWhatsThis(self.tr( + """<b>Ask for Server Start Options</b>""" + """<p>Asks for server start options before the Flask Web server""" + """ is started. If this is unchecked, the server is started with""" + """ default parameters.</p>""" + )) + self.askForServerOptionsAct.setCheckable(True) + self.actions.append(self.askForServerOptionsAct) + + ############################### + ## shell action below ## + ############################### + + self.runPythonShellAct = E5Action( + self.tr('Start Flask Python Console'), + self.tr('Start Flask &Python Console'), + 0, 0, + self, 'flask_python_console') + self.runPythonShellAct.setStatusTip(self.tr( + 'Starts an interactive Python interpreter')) + self.runPythonShellAct.setWhatsThis(self.tr( + """<b>Start Flask Python Console</b>""" + """<p>Starts an interactive Python interpreter.</p>""" + )) + self.runPythonShellAct.triggered.connect(self.__runPythonShell) + self.actions.append(self.runPythonShellAct) + + ################################ ## routes action below ## - ############################## + ################################ self.showRoutesAct = E5Action( self.tr('Show Routes'), @@ -120,6 +155,25 @@ self.actions.append(self.showRoutesAct) ################################## + ## database action below ## + ################################## + + self.initDatabaseAct = E5Action( + self.tr('Initialize Database'), + self.tr('&Initialize Database'), + 0, 0, + self, 'flask_init_database') + self.initDatabaseAct.setStatusTip(self.tr( + 'Shows a dialog with the result of the database creation')) + self.initDatabaseAct.setWhatsThis(self.tr( + """<b>Initialize Database</b>""" + """<p>Shows a dialog with the result of the database""" + """ creation.</p>""" + )) + self.initDatabaseAct.triggered.connect(self.__initDatabase) + self.actions.append(self.initDatabaseAct) + + ################################## ## documentation action below ## ################################## @@ -167,11 +221,17 @@ menu = QMenu(self.tr('&Flask'), self.__ui) menu.setTearOffEnabled(True) + menu.addSection("flask run") menu.addAction(self.runServerAct) menu.addAction(self.runDevServerAct) - menu.addSeparator() + menu.addAction(self.askForServerOptionsAct) + menu.addSection("flask shell") + menu.addAction(self.runPythonShellAct) + menu.addSection("flask routes") menu.addAction(self.showRoutesAct) - menu.addSeparator() + menu.addSection("flask init-db") + menu.addAction(self.initDatabaseAct) + menu.addSection(self.tr("Various")) menu.addAction(self.documentationAct) menu.addSeparator() menu.addAction(self.aboutFlaskAct) @@ -421,7 +481,7 @@ self.__ui.launchHelpViewer(page) ################################################################## - ## slots below implement run functions + ## slots below implement run functions for the server ################################################################## @pyqtSlot() @@ -435,8 +495,10 @@ if self.__serverDialog is not None: self.__serverDialog.close() - dlg = RunServerDialog(self.__plugin) - if dlg.startServer(self, development=development): + askForOptions = self.askForServerOptionsAct.isChecked() + dlg = RunServerDialog(self.__plugin, self) + if dlg.startServer(development=development, + askForOptions=askForOptions): dlg.show() self.__serverDialog = dlg @@ -447,31 +509,60 @@ """ self.__runServer(development=True) - # TODO: add method to start a server with parameters + ################################################################## + ## slots below implement functions for the flask console + ################################################################## -## def __serverProcFinished(self): -## """ -## Private slot connected to the finished signal. -## """ -## if ( -## self.__serverProc is not None and -## self.__serverProc.state() != QProcess.NotRunning -## ): -## self.__serverProc.terminate() -## QTimer.singleShot(2000, self.__serverProc.kill) -## self.__serverProc.waitForFinished(3000) -## self.__serverProc = None - + @pyqtSlot() def __runPythonShell(self): """ Private slot to start a Python console in the app context. """ - # TODO: implement this (flask shell) + workdir, env = self.prepareRuntimeEnvironment() + if env is not None: + command = self.getFlaskCommand() + + consoleCmd = self.__plugin.getPreferences("ConsoleCommand") + if consoleCmd: + self.__terminatePythonShell() + + args = Utilities.parseOptionString(consoleCmd) + args[0] = Utilities.getExecutablePath(args[0]) + args += [command, "shell"] + + self.__shellProcess = QProcess() + self.__shellProcess.setProcessEnvironment(env) + self.__shellProcess.setWorkingDirectory(workdir) + self.__shellProcess.finished.connect( + self.__shellProcessFinished) + + self.__shellProcess.start(args[0], args[1:]) + self.__shellProcess.waitForStarted(10000) + + @pyqtSlot() + def __shellProcessFinished(self): + """ + Private slot connected to the finished signal. + """ + self.__shellProcess = None + + def __terminatePythonShell(self): + """ + Private method to terminate the current Python console. + """ + if ( + self.__shellProcess is not None and + self.__shellProcess.state() != QProcess.NotRunning + ): + self.__shellProcess.terminate() + QTimer.singleShot(2000, self.__shellProcess.kill) + self.__shellProcess.waitForFinished(3000) ################################################################## ## slots below implement various debugging functions ################################################################## + @pyqtSlot() def __showRoutes(self): """ Private slot showing all URL dispatch routes. @@ -483,3 +574,12 @@ if dlg.showRoutes(): dlg.show() self.__routesDialog = dlg + + @pyqtSlot() + def __initDatabase(self): + """ + Private slot showing the result of the database creation. + """ + dlg = FlaskCommandDialog(self) + if dlg.startCommand("init-db"): + dlg.exec()
--- a/ProjectFlask/RunServerDialog.py Fri Nov 13 19:51:28 2020 +0100 +++ b/ProjectFlask/RunServerDialog.py Sat Nov 14 19:56:06 2020 +0100 @@ -11,7 +11,7 @@ from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer from PyQt5.QtGui import QTextCharFormat -from PyQt5.QtWidgets import QDialog, QDialogButtonBox +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMenu from E5Gui import E5MessageBox from E5Gui.E5Application import e5App @@ -19,6 +19,9 @@ from .Ui_RunServerDialog import Ui_RunServerDialog from . import AnsiTools +from .ServerStartOptionsDialog import ServerStartOptionsDialog + +import UI.PixmapCache # TODO: should this be placed into the sidebar as a sidebar widget? @@ -26,12 +29,14 @@ """ Class implementing a dialog to run the Flask server. """ - def __init__(self, plugin, parent=None): + def __init__(self, plugin, project, parent=None): """ Constructor @param plugin reference to the plug-in object @type PluginProjectFlask + @param project reference to the project object + @type Project @param parent reference to the parent widget @type QWidget """ @@ -39,11 +44,16 @@ self.setupUi(self) self.__plugin = plugin + self.__project = project + + self.__serverOptions = { + "development": False + } self.__process = None self.__serverUrl = "" - self.__ansiRe = re.compile(r"""(\\x1b\[\d+m)""") + self.__ansiRe = re.compile("(\\x1b\[\d+m)") self.__urlRe = re.compile(r""" * Running on ([^(]+) \(.*""") @@ -51,22 +61,67 @@ self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) self.__defaultTextFormat = self.outputEdit.currentCharFormat() + + self.__initActionsMenu() - def startServer(self, project, development=False): + def __initActionsMenu(self): + """ + Private method to populate the actions button menu. + """ + self.__actionsMenu = QMenu() + self.__actionsMenu.setTearOffEnabled(True) + self.__actionsMenu.setToolTipsVisible(True) + self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu) + + # re-start server + self.__actionsMenu.addAction( + self.tr("Re-start Server"), self.__restartServer) + self.__restartModeAct = self.__actionsMenu.addAction( + self.tr("Re-start Server"), self.__restartServerDifferentMode) + self.__actionsMenu.addSeparator() + self.__actionsMenu.addAction( + self.tr("Re-start Server with Options"), + self.__restartServerWithOptions) + # start server with options + + self.menuButton.setIcon(UI.PixmapCache.getIcon("actionsToolButton")) + self.menuButton.setMenu(self.__actionsMenu) + + @pyqtSlot() + def __showActionsMenu(self): + """ + Private slot handling the actions menu about to be shown. + """ + if self.__serverOptions["development"]: + self.__restartModeAct.setText( + self.tr("Re-start Server (Production Mode)")) + else: + self.__restartModeAct.setText( + self.tr("Re-start Server (Development Mode)")) + + def startServer(self, development=False, restart=False, + askForOptions=False): """ Public method to start the Flask server process. - @param project reference to the project object - @type Project @param development flag indicating development mode @type bool @return flag indicating success @rtype bool """ - workdir, env = project.prepareRuntimeEnvironment( - development=development) + self.__serverOptions["development"] = development + + if askForOptions: + dlg = ServerStartOptionsDialog(self.__serverOptions) + if dlg.exec() != QDialog.Accepted: + return False + + self.__serverOptions.update(dlg.getDataDict()) + + workdir, env = self.__project.prepareRuntimeEnvironment( + development=self.__serverOptions["development"]) if env is not None: - command = project.getFlaskCommand() + command = self.__project.getFlaskCommand() self.__process = QProcess() self.__process.setProcessEnvironment(env) @@ -76,11 +131,13 @@ self.__process.readyReadStandardOutput.connect(self.__readStdOut) self.__process.finished.connect(self.__processFinished) + self.outputEdit.clear() + args = ["run"] -# if host: -# args += ["--host", host] -# if port: -# args += ["--port", str(port)] + if "host" in self.__serverOptions and self.__serverOptions["host"]: + args += ["--host", self.__serverOptions["host"]] + if "port" in self.__serverOptions and self.__serverOptions["port"]: + args += ["--port", self.__serverOptions["port"]] self.__process.start(command, args) ok = self.__process.waitForStarted(10000) @@ -94,6 +151,7 @@ self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) self.stopServerButton.setEnabled(True) self.stopServerButton.setDefault(True) + self.startBrowserButton.setEnabled(True) else: ok = False @@ -120,7 +178,6 @@ urlMatch = self.__urlRe.search(out) if urlMatch: self.__serverUrl = urlMatch.group(1) - self.startBrowserButton.setEnabled(True) for txt in self.__ansiRe.split(out): if txt.startswith("\x1b["): @@ -180,3 +237,36 @@ else: e5App().getObject("UserInterface").launchHelpViewer( self.__serverUrl) + + @pyqtSlot() + def __restartServer(self): + """ + Private slot to restart the server process. + """ + # step 1: stop the current server + self.on_stopServerButton_clicked() + + # step 2: start a new server + self.startServer(development=self.__serverOptions["development"]) + + @pyqtSlot() + def __restartServerDifferentMode(self): + """ + Private slot to restart the server process with the opposite mode. + """ + # step 1: stop the current server + self.on_stopServerButton_clicked() + + # step 2: start a new server + self.startServer(development=not self.__serverOptions["development"]) + @pyqtSlot() + def __restartServerWithOptions(self): + """ + Private slot to restart the server asking for start options. + """ + # step 1: stop the current server + self.on_stopServerButton_clicked() + + # step 2: start a new server + self.startServer(development=self.__serverOptions["development"], + askForOptions=True)
--- a/ProjectFlask/RunServerDialog.ui Fri Nov 13 19:51:28 2020 +0100 +++ b/ProjectFlask/RunServerDialog.ui Sat Nov 14 19:56:06 2020 +0100 @@ -45,6 +45,13 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout"> <item> + <widget class="QToolButton" name="menuButton"> + <property name="popupMode"> + <enum>QToolButton::InstantPopup</enum> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="startBrowserButton"> <property name="enabled"> <bool>false</bool> @@ -99,6 +106,7 @@ </widget> <tabstops> <tabstop>outputEdit</tabstop> + <tabstop>menuButton</tabstop> <tabstop>startBrowserButton</tabstop> <tabstop>stopServerButton</tabstop> </tabstops>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/ServerStartOptionsDialog.py Sat Nov 14 19:56:06 2020 +0100 @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter parameters to start the server. +""" + +from PyQt5.QtWidgets import QDialog + +from .Ui_ServerStartOptionsDialog import Ui_ServerStartOptionsDialog + + +class ServerStartOptionsDialog(QDialog, Ui_ServerStartOptionsDialog): + """ + Class implementing a dialog to enter parameters to start the server. + """ + def __init__(self, options, parent=None): + """ + Constructor + + @param options dictionary containing the current server start options + @type dict + @param parent reference to the parent widget + @type QWidget + """ + super(ServerStartOptionsDialog, self).__init__(parent) + self.setupUi(self) + + self.developmentCheckBox.setChecked(options.get("development", False)) + self.hostEdit.setText(options.get("host", "")) + self.portSpinBox.setValue(int(options.get("port", "5000"))) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def getDataDict(self): + """ + Public method to get a dictionary containing the entered data. + + @return dictionary containing the entered data + @rtype dict + """ + options = {} + + options["development"] = self.developmentCheckBox.isChecked() + host = self.hostEdit.text() + if host: + options["host"] = host + port = self.portSpinBox.value() + if port != 5000: + options["port"] = str(port) + + return options
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/ServerStartOptionsDialog.ui Sat Nov 14 19:56:06 2020 +0100 @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ServerStartOptionsDialog</class> + <widget class="QDialog" name="ServerStartOptionsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>141</height> + </rect> + </property> + <property name="windowTitle"> + <string>Server Parameters</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="3"> + <widget class="QCheckBox" name="developmentCheckBox"> + <property name="toolTip"> + <string>Select to start the server in development mode</string> + </property> + <property name="text"> + <string>Development Mode</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Host:</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="hostEdit"> + <property name="toolTip"> + <string>Enter the interface to bind to</string> + </property> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="portSpinBox"> + <property name="toolTip"> + <string>Enter the port to bind to</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1025</number> + </property> + <property name="maximum"> + <number>65565</number> + </property> + </widget> + </item> + <item row="2" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>188</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>developmentCheckBox</tabstop> + <tabstop>hostEdit</tabstop> + <tabstop>portSpinBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ServerStartOptionsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ServerStartOptionsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>