ProjectFlask/Project.py

Wed, 21 Sep 2022 16:30:15 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 21 Sep 2022 16:30:15 +0200
branch
eric7
changeset 70
22e1d0f69668
parent 66
0d3168d0e310
child 72
4557829a4acf
permissions
-rw-r--r--

Reformatted source code with 'Black'.

# -*- coding: utf-8 -*-

# Copyright (c) 2020 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the Flask project support.
"""

import os

from PyQt6.QtCore import (
    pyqtSlot,
    QObject,
    QProcess,
    QProcessEnvironment,
    QTimer,
    QFileInfo,
)
from PyQt6.QtWidgets import QMenu, QDialog

from EricWidgets import EricMessageBox, EricFileDialog
from EricGui.EricAction import EricAction
from EricWidgets.EricApplication import ericApp

from Globals import isWindowsPlatform

import UI.PixmapCache
import Utilities

from .FlaskBabelExtension.PyBabelProjectExtension import PyBabelProject
from .FlaskMigrateExtension.MigrateProjectExtension import MigrateProject


class Project(QObject):
    """
    Class implementing the Flask project support.
    """

    def __init__(self, plugin, iconSuffix, parent=None):
        """
        Constructor

        @param plugin reference to the plugin object
        @type ProjectFlaskPlugin
        @param iconSuffix suffix for the icons
        @type str
        @param parent parent
        @type QObject
        """
        super().__init__(parent)

        self.__plugin = plugin
        self.__iconSuffix = iconSuffix
        self.__ui = parent

        self.__ericProject = ericApp().getObject("Project")
        self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager")

        self.__menus = {}  # dictionary with references to menus
        self.__formsBrowser = None
        self.__hooksInstalled = False

        self.__serverDialog = None
        self.__routesDialog = None
        self.__shellProcess = None

        self.__projectData = {
            "flask": {},
            "flask-babel": {},
            "flask-migrate": {},
        }

        self.__flaskVersions = {
            "python": "",
            "flask": "",
            "werkzeug": "",
        }

        self.__capabilities = {}

        self.__pybabelProject = PyBabelProject(self.__plugin, self, self)
        self.__migrateProject = MigrateProject(self.__plugin, self, self)

    def initActions(self):
        """
        Public method to define the Flask actions.
        """
        self.actions = []

        ##############################
        ## run actions below        ##
        ##############################

        self.runServerAct = EricAction(
            self.tr("Run Server"),
            self.tr("Run &Server"),
            0,
            0,
            self,
            "flask_run_server",
        )
        self.runServerAct.setStatusTip(self.tr("Starts the Flask Web server"))
        self.runServerAct.setWhatsThis(
            self.tr("""<b>Run Server</b>""" """<p>Starts the Flask Web server.</p>""")
        )
        self.runServerAct.triggered.connect(self.__runServer)
        self.actions.append(self.runServerAct)

        self.runDevServerAct = EricAction(
            self.tr("Run Development Server"),
            self.tr("Run &Development Server"),
            0,
            0,
            self,
            "flask_run_dev_server",
        )
        self.runDevServerAct.setStatusTip(
            self.tr("Starts the Flask Web server in development mode")
        )
        self.runDevServerAct.setWhatsThis(
            self.tr(
                """<b>Run Development Server</b>"""
                """<p>Starts the Flask Web server in development mode.</p>"""
            )
        )
        self.runDevServerAct.triggered.connect(self.__runDevelopmentServer)
        self.actions.append(self.runDevServerAct)

        self.askForServerOptionsAct = EricAction(
            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 = EricAction(
            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 = EricAction(
            self.tr("Show Routes"),
            self.tr("Show &Routes"),
            0,
            0,
            self,
            "flask_show_routes",
        )
        self.showRoutesAct.setStatusTip(
            self.tr("Shows a dialog with the routes of the flask app")
        )
        self.showRoutesAct.setWhatsThis(
            self.tr(
                """<b>Show Routes</b>"""
                """<p>Shows a dialog with the routes of the flask app.</p>"""
            )
        )
        self.showRoutesAct.triggered.connect(self.__showRoutes)
        self.actions.append(self.showRoutesAct)

        ##################################
        ## documentation action below   ##
        ##################################

        self.documentationAct = EricAction(
            self.tr("Documentation"),
            self.tr("D&ocumentation"),
            0,
            0,
            self,
            "flask_documentation",
        )
        self.documentationAct.setStatusTip(
            self.tr("Shows the help viewer with the Flask documentation")
        )
        self.documentationAct.setWhatsThis(
            self.tr(
                """<b>Documentation</b>"""
                """<p>Shows the help viewer with the Flask documentation.</p>"""
            )
        )
        self.documentationAct.triggered.connect(self.__showDocumentation)
        self.actions.append(self.documentationAct)

        ##############################
        ## about action below       ##
        ##############################

        self.aboutFlaskAct = EricAction(
            self.tr("About Flask"), self.tr("About &Flask"), 0, 0, self, "flask_about"
        )
        self.aboutFlaskAct.setStatusTip(self.tr("Shows some information about Flask"))
        self.aboutFlaskAct.setWhatsThis(
            self.tr(
                """<b>About Flask</b>"""
                """<p>Shows some information about Flask.</p>"""
            )
        )
        self.aboutFlaskAct.triggered.connect(self.__flaskInfo)
        self.actions.append(self.aboutFlaskAct)

        self.__pybabelProject.initActions()
        self.__migrateProject.initActions()

        ######################################
        ## configuration action below       ##
        ######################################

        self.flaskConfigAct = EricAction(
            self.tr("Configure Flask for Project"),
            self.tr("Configure Flask for &Project"),
            0,
            0,
            self,
            "flask_config_for_project",
        )
        self.flaskConfigAct.setStatusTip(
            self.tr("Shows a dialog to edit the project specific flask configuration")
        )
        self.flaskConfigAct.setWhatsThis(
            self.tr(
                """<b>Configure Flask for Project</b>"""
                """<p>Shows a dialog to edit the project specific flask"""
                """ configuration.</p>"""
            )
        )
        self.flaskConfigAct.triggered.connect(self.__configureFlaskForProject)
        self.actions.append(self.flaskConfigAct)

    def initMenu(self):
        """
        Public method to initialize the Flask menu.

        @return the menu generated
        @rtype QMenu
        """
        self.__menus = {}  # clear menus references

        self.__menus["flask-babel"] = self.__pybabelProject.initMenu()
        self.__menus["flask-migrate"] = self.__migrateProject.initMenu()

        menu = QMenu(self.tr("&Flask"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.flaskConfigAct)
        menu.addSeparator()
        menu.addAction(self.runServerAct)
        menu.addAction(self.runDevServerAct)
        menu.addAction(self.askForServerOptionsAct)
        menu.addSeparator()
        menu.addAction(self.runPythonShellAct)
        menu.addSeparator()
        menu.addAction(self.showRoutesAct)
        menu.addSeparator()
        menu.addMenu(self.__menus["flask-migrate"])
        menu.addSeparator()
        menu.addMenu(self.__menus["flask-babel"])
        menu.addSeparator()
        menu.addAction(self.documentationAct)
        menu.addSeparator()
        menu.addAction(self.aboutFlaskAct)

        self.__menus["main"] = menu

        return menu

    def getMenu(self, name):
        """
        Public method to get a reference to the requested menu.

        @param name name of the menu
        @type str
        @return reference to the menu or None, if no menu with the given
            name exists
        @rtype QMenu or None
        """
        if name in self.__menus:
            return self.__menus[name]
        else:
            return None

    def getMenuNames(self):
        """
        Public method to get the names of all menus.

        @return menu names
        @rtype list of str
        """
        return list(self.__menus.keys())

    def projectOpenedHooks(self):
        """
        Public method to add our hook methods.
        """
        if self.__ericProject.getProjectType() == "Flask":
            self.__formsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("forms")
            )
            self.__formsBrowser.addHookMethodAndMenuEntry(
                "newForm", self.newForm, self.tr("New template...")
            )

            self.__determineCapabilities()
            self.__setDebugEnvironment()

            self.__pybabelProject.projectOpenedHooks()

            self.__hooksInstalled = True

    def projectClosedHooks(self):
        """
        Public method to remove our hook methods.
        """
        self.__pybabelProject.projectClosedHooks()

        if self.__hooksInstalled:
            self.__formsBrowser.removeHookMethod("newForm")
            self.__formsBrowser = None

        self.__hooksInstalled = False

    def newForm(self, dirPath):
        """
        Public method to create a new form.

        @param dirPath full directory path for the new form file
        @type str
        """
        from .FormSelectionDialog import FormSelectionDialog

        dlg = FormSelectionDialog()
        if dlg.exec() == QDialog.DialogCode.Accepted:
            template = dlg.getTemplateText()

            fileFilters = self.tr(
                "HTML Files (*.html);;" "HTML Files (*.htm);;" "All Files (*)"
            )
            fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
                self.__ui,
                self.tr("New Form"),
                dirPath,
                fileFilters,
                None,
                EricFileDialog.Options(EricFileDialog.DontConfirmOverwrite),
            )
            if fname:
                ext = QFileInfo(fname).suffix()
                if not ext:
                    ex = selectedFilter.split("(*")[1].split(")")[0]
                    if ex:
                        fname += ex

                if os.path.exists(fname):
                    res = EricMessageBox.yesNo(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr("""The file already exists! Overwrite""" """ it?"""),
                        icon=EricMessageBox.Warning,
                    )
                    if not res:
                        # user selected to not overwrite
                        return

                try:
                    with open(fname, "w", encoding="utf-8") as f:
                        f.write(template)
                except OSError as err:
                    EricMessageBox.critical(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr(
                            "<p>The new form file <b>{0}</b> could"
                            " not be created.</p><p>Problem: {1}</p>"
                        ).format(fname, str(err)),
                    )
                    return

                self.__ericProject.appendFile(fname)
                self.__formsBrowser.sourceFile.emit(fname)

    ##################################################################
    ## methods below implement virtual environment handling
    ##################################################################

    def getVirtualEnvironment(self):
        """
        Public method to get the path of the virtual environment.

        @return path of the virtual environment
        @rtype str
        """
        language = self.__ericProject.getProjectLanguage()
        if language == "Python3":
            # get project specific virtual environment name
            venvName = self.getData("flask", "virtual_environment_name")
            if not venvName:
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
        else:
            venvName = ""
        virtEnv = (
            self.__virtualEnvManager.getVirtualenvDirectory(venvName)
            if venvName
            else ""
        )

        if virtEnv and not os.path.exists(virtEnv):
            virtEnv = ""

        return virtEnv  # __IGNORE_WARNING_M834__

    def getVirtualenvInterpreter(self):
        """
        Public method to get the path of the Python interpreter to be used
        with the current project.

        @return path of the Python interpreter
        @rtype str
        """
        return self.getFullCommand("python")

    def getFullCommand(self, command, virtualEnvPath=None):
        """
        Public method to get the full command for a given command name.

        @param command command name
        @type str
        @param virtualEnvPath path of the virtual environment
        @type str
        @return full command
        @rtype str
        """
        virtualEnv = virtualEnvPath or self.getVirtualEnvironment()
        fullCmds = (
            [
                os.path.join(virtualEnv, "Scripts", command + ".exe"),
                os.path.join(virtualEnv, "bin", command + ".exe"),
                command,
            ]  # fall back to just cmd
            if isWindowsPlatform()
            else [
                os.path.join(virtualEnv, "bin", command),
                os.path.join(virtualEnv, "local", "bin", command),
                Utilities.getExecutablePath(command),
                command,
            ]  # fall back to just cmd
        )
        for command in fullCmds:
            if os.path.exists(command):
                break
        return command

    ##################################################################
    ## methods below implement general functionality
    ##################################################################

    def projectClosed(self):
        """
        Public method to handle the closing of a project.
        """
        for dlg in (self.__serverDialog, self.__routesDialog):
            if dlg is not None:
                dlg.close()

        self.__migrateProject.projectClosed()

    def supportedPythonVariants(self):
        """
        Public method to get the supported Python variants.

        @return list of supported Python variants
        @rtype list of str
        """
        variants = []

        virtEnv = self.getVirtualEnvironment()
        if virtEnv:
            fullCmd = self.getFlaskCommand()
            if fullCmd:
                variants.append("Python3")
        else:
            fullCmd = self.getFlaskCommand()
            if isWindowsPlatform():
                if fullCmd:
                    variants.append("Python3")
            else:
                fullCmds = Utilities.getExecutablePaths("flask")
                for fullCmd in fullCmds:
                    try:
                        with open(fullCmd, "r", encoding="utf-8") as f:
                            l0 = f.readline()
                    except OSError:
                        l0 = ""
                    if self.__isSuitableForVariant("Python3", l0):
                        variants.append("Python3")
                        break

        return variants

    def __isSuitableForVariant(self, variant, line0):
        """
        Private method to test, if a detected command file is suitable for the
        given Python variant.

        @param variant Python variant to test for
        @type str
        @param line0 first line of the executable
        @type str
        @return flag indicating a suitable file was found
        @rtype bool
        """
        l0 = line0.lower()
        ok = variant.lower() in l0 or "{0}.".format(variant[-1]) in l0
        ok |= "pypy3" in l0

        return ok

    def getFlaskCommand(self):
        """
        Public method to build the Flask command.

        @return full flask command
        @rtype str
        """
        return self.getFullCommand("flask")

    @pyqtSlot()
    def __flaskInfo(self):
        """
        Private slot to show some info about Flask.
        """
        versions = self.getFlaskVersionStrings()
        url = "https://palletsprojects.com/p/flask/"

        msgBox = EricMessageBox.EricMessageBox(
            EricMessageBox.Question,
            self.tr("About Flask"),
            self.tr(
                "<p>Flask is a lightweight WSGI web application framework."
                " It is designed to make getting started quick and easy,"
                " with the ability to scale up to complex applications.</p>"
                "<p><table>"
                "<tr><td>Flask Version:</td><td>{0}</td></tr>"
                "<tr><td>Werkzeug Version:</td><td>{1}</td></tr>"
                "<tr><td>Python Version:</td><td>{2}</td></tr>"
                '<tr><td>Flask URL:</td><td><a href="{3}">'
                "The Pallets Projects - Flask</a></td></tr>"
                "</table></p>",
                "Do not translate the program names.",
            ).format(versions["flask"], versions["werkzeug"], versions["python"], url),
            modal=True,
            buttons=EricMessageBox.Ok,
        )
        msgBox.setIconPixmap(
            UI.PixmapCache.getPixmap(
                os.path.join(
                    "ProjectFlask", "icons", "flask64-{0}".format(self.__iconSuffix)
                )
            )
        )
        msgBox.exec()

    def getFlaskVersionStrings(self):
        """
        Public method to get the Flask, Werkzeug and Python versions as a
        string.

        @return dictionary containing the Flask, Werkzeug and Python versions
        @rtype dict
        """
        if not self.__flaskVersions["flask"]:
            cmd = self.getFlaskCommand()
            proc = QProcess()
            proc.start(cmd, ["--version"])
            if proc.waitForFinished(10000):
                output = str(proc.readAllStandardOutput(), "utf-8")
                for line in output.lower().splitlines():
                    key, version = line.strip().split(None, 1)
                    self.__flaskVersions[key] = version

        return self.__flaskVersions

    def prepareRuntimeEnvironment(self, development=False):
        """
        Public method to prepare a QProcessEnvironment object and determine
        the appropriate working directory.

        @param development flag indicating development mode
        @type bool
        @return tuple containing the working directory and a prepared
            environment object to be used with QProcess
        @rtype tuple of (str, QProcessEnvironment)
        """
        workdir, app = self.getApplication()
        env = QProcessEnvironment.systemEnvironment()
        env.insert("FLASK_APP", app)
        if development:
            env.insert("FLASK_ENV", "development")

        return workdir, env

    def getApplication(self):
        """
        Public method to determine the application name and the respective
        working directory.

        @return tuple containing the working directory and the application name
        @rtype tuple of (str, str)
        """
        mainScript = self.__ericProject.getMainScript(normalized=True)
        if not mainScript:
            EricMessageBox.critical(
                self.__ui,
                self.tr("Prepare Environment"),
                self.tr(
                    """The project has no configured main script"""
                    """ (= Flask application). Aborting..."""
                ),
            )
            return "", None

        scriptPath, scriptName = os.path.split(mainScript)
        if scriptName == "__init__.py":
            workdir, app = os.path.split(scriptPath)
        else:
            workdir, app = scriptPath, scriptName
        return workdir, app

    def getData(self, category, key):
        """
        Public method to get data stored in the project store.

        @param category data category
        @type str
        @param key data key
        @type str
        @return referenced data
        @rtype any
        """
        if category not in self.__projectData:
            self.__projectData[category] = {}

        if not self.__projectData[category]:
            data = self.__ericProject.getData("PROJECTTYPESPECIFICDATA", category)
            if data is not None:
                self.__projectData[category] = data

        data = self.__projectData[category]
        if not key:
            # return complete category dictionary
            return data
        elif key in data:
            # return individual entry
            return data[key]
        else:
            # failure
            return None

    def setData(self, category, key, value):
        """
        Public method to store data in the project store.

        @param category data category
        @type str
        @param key data key
        @type str
        @param value data to be stored
        @type any (serializable type)
        """
        if category not in self.__projectData:
            self.__projectData[category] = {}

        if not self.__projectData[category]:
            data = self.__ericProject.getData("PROJECTTYPESPECIFICDATA", category)
            if data is not None:
                self.__projectData[category] = data

        if not key:
            # update the complete category
            self.__projectData[category] = value
        else:
            # update individual entry
            self.__projectData[category][key] = value

        self.__ericProject.setData(
            "PROJECTTYPESPECIFICDATA", category, self.__projectData[category]
        )

    def __determineCapabilities(self):
        """
        Private method to determine capabilities provided by supported
        extensions.
        """
        # 1. support for flask-babel (i.e. pybabel)
        self.__pybabelProject.determineCapability()

        # 2. support for flask-migrate
        self.__migrateProject.determineCapability()

    def hasCapability(self, key):
        """
        Public method to check, if a capability is available.

        @param key key of the capability to check
        @type str
        @return flag indicating the availability of the capability
        @rtype bool
        """
        try:
            return self.__capabilities[key]
        except KeyError:
            return False

    def setCapability(self, key, available):
        """
        Public method to set the availability status of a capability.

        @param key key of the capability to set
        @type str
        @param available flag indicating the availability of the capability
        @type bool
        """
        self.__capabilities[key] = available

    ##################################################################
    ## slots below implements project specific flask configuration
    ##################################################################

    @pyqtSlot()
    def __configureFlaskForProject(self):
        """
        Private slot to configure the project specific flask parameters.
        """
        from .FlaskConfigDialog import FlaskConfigDialog

        config = self.getData("flask", "")
        dlg = FlaskConfigDialog(config, self)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            config = dlg.getConfiguration()
            self.setData("flask", "", config)
            self.__setIgnoreVirtualEnvironment()
            self.__setDebugEnvironment()

            self.__migrateProject.determineCapability()

            self.__pybabelProject.determineCapability()
            self.projectClosedHooks()
            self.projectOpenedHooks()

    def __setIgnoreVirtualEnvironment(self):
        """
        Private method to add an embedded project specific virtual environment
        to the list of ignore files/directories.
        """
        virtenvName = self.getData("flask", "virtual_environment_name")
        if virtenvName:
            virtenvPath = self.getVirtualEnvironment()
            if self.__ericProject.startswithProjectPath(virtenvPath):
                relVirtenvPath = self.__ericProject.getRelativeUniversalPath(
                    virtenvPath
                )
                if relVirtenvPath not in self.__ericProject.pdata["FILETYPES"]:
                    self.__ericProject.pdata["FILETYPES"][relVirtenvPath] = "__IGNORE__"
                    self.__ericProject.setDirty(True)

    def __setDebugEnvironment(self):
        """
        Private method to set the virtual environment as the selected debug
        environment.
        """
        language = self.__ericProject.getProjectLanguage()
        if language == "Python3":
            # get project specific virtual environment name
            venvName = self.getData("flask", "virtual_environment_name")
            if not venvName:
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
            if venvName:
                self.__ericProject.debugProperties["VIRTUALENV"] = venvName

    ##################################################################
    ## slot below implements documentation function
    ##################################################################

    def __showDocumentation(self):
        """
        Private slot to show the helpviewer with the Flask documentation.
        """
        page = self.__plugin.getPreferences("FlaskDocUrl")
        self.__ui.launchHelpViewer(page)

    ##################################################################
    ## slots below implement run functions for the server
    ##################################################################

    @pyqtSlot()
    def __runServer(self, development=False):
        """
        Private slot to start the Flask Web server.

        @param development flag indicating development mode
        @type bool
        """
        from .RunServerDialog import RunServerDialog

        if self.__serverDialog is not None:
            self.__serverDialog.close()

        askForOptions = self.askForServerOptionsAct.isChecked()
        dlg = RunServerDialog(self.__plugin, self)
        if dlg.startServer(development=development, askForOptions=askForOptions):
            dlg.show()
            self.__serverDialog = dlg

    @pyqtSlot()
    def __runDevelopmentServer(self):
        """
        Private slot to start the Flask Web server in development mode.
        """
        self.__runServer(development=True)

    ##################################################################
    ## slots below implement functions for the flask console
    ##################################################################

    @pyqtSlot()
    def __runPythonShell(self):
        """
        Private slot to start a Python console in the app context.
        """
        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.ProcessState.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.
        """
        from .RoutesDialog import RoutesDialog

        if self.__routesDialog is not None:
            self.__routesDialog.close()

        dlg = RoutesDialog(self)
        if dlg.showRoutes():
            dlg.show()
            self.__routesDialog = dlg

eric ide

mercurial