Added code for the basic project support.

Sun, 08 Nov 2020 17:54:22 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 08 Nov 2020 17:54:22 +0100
changeset 2
6cc80e4db8a7
parent 1
d6557db39585
child 3
265c3c2914e2

Added code for the basic project support.

.hgignore file | annotate | diff | comparison | revisions
PluginFlask.e4p file | annotate | diff | comparison | revisions
PluginProjectFlask.py file | annotate | diff | comparison | revisions
ProjectFlask/ConfigurationPage/FlaskPage.py file | annotate | diff | comparison | revisions
ProjectFlask/ConfigurationPage/FlaskPage.ui file | annotate | diff | comparison | revisions
ProjectFlask/ConfigurationPage/__init__.py file | annotate | diff | comparison | revisions
ProjectFlask/Project.py file | annotate | diff | comparison | revisions
ProjectFlask/__init__.py file | annotate | diff | comparison | revisions
ProjectFlask/icons/flask-dark.svg file | annotate | diff | comparison | revisions
ProjectFlask/icons/flask-light.svg file | annotate | diff | comparison | revisions
ProjectFlask/icons/flask64-dark.svg file | annotate | diff | comparison | revisions
ProjectFlask/icons/flask64-light.svg file | annotate | diff | comparison | revisions
--- a/.hgignore	Sun Nov 08 17:53:06 2020 +0100
+++ b/.hgignore	Sun Nov 08 17:54:22 2020 +0100
@@ -11,3 +11,4 @@
 glob:tmp
 glob:__pycache__
 glob:**.DS_Store
+glob:Ui_*.py
--- a/PluginFlask.e4p	Sun Nov 08 17:53:06 2020 +0100
+++ b/PluginFlask.e4p	Sun Nov 08 17:54:22 2020 +0100
@@ -11,14 +11,27 @@
   <Version>1.x</Version>
   <Author>Detlev Offenbach</Author>
   <Email>detlev@die-offenbachs.de</Email>
-  <TranslationPattern>ProjectDjango/i18n/flask_%language%.ts</TranslationPattern>
+  <TranslationPattern>ProjectFlask/i18n/flask_%language%.ts</TranslationPattern>
   <Eol index="1"/>
   <Sources>
     <Source>PluginProjectFlask.py</Source>
+    <Source>ProjectFlask/ConfigurationPage/FlaskPage.py</Source>
+    <Source>ProjectFlask/ConfigurationPage/__init__.py</Source>
+    <Source>ProjectFlask/Project.py</Source>
+    <Source>ProjectFlask/__init__.py</Source>
     <Source>__init__.py</Source>
   </Sources>
+  <Forms>
+    <Form>ProjectFlask/ConfigurationPage/FlaskPage.ui</Form>
+  </Forms>
   <Others>
     <Other>.hgignore</Other>
+    <Other>PluginFlask.e4p</Other>
+    <Other>ProjectFlask/APIs</Other>
+    <Other>ProjectFlask/icons/flask-dark.svg</Other>
+    <Other>ProjectFlask/icons/flask-light.svg</Other>
+    <Other>ProjectFlask/icons/flask64-dark.svg</Other>
+    <Other>ProjectFlask/icons/flask64-light.svg</Other>
   </Others>
   <MainScript>PluginProjectFlask.py</MainScript>
   <Vcs>
@@ -143,6 +156,7 @@
     <FiletypeAssociation pattern="Makefile" type="OTHERS"/>
     <FiletypeAssociation pattern="README" type="OTHERS"/>
     <FiletypeAssociation pattern="README.*" type="OTHERS"/>
+    <FiletypeAssociation pattern="Ui_*.py" type="__IGNORE__"/>
     <FiletypeAssociation pattern="makefile" type="OTHERS"/>
   </FiletypeAssociations>
 </Project>
--- a/PluginProjectFlask.py	Sun Nov 08 17:53:06 2020 +0100
+++ b/PluginProjectFlask.py	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,461 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Flask project plugin.
+"""
+
+import os
+import glob
+import fnmatch
+
+from PyQt5.QtCore import QCoreApplication, QObject, QTranslator
+
+from E5Gui.E5Application import e5App
+
+import Preferences
+
+from Globals import isWindowsPlatform, isMacPlatform
+
+from ProjectFlask.Project import Project
+
+# Start-of-Header
+name = "Flask Project Plugin"
+author = "Detlev Offenbach <detlev@die-offenbachs.de>"
+autoactivate = True
+deactivateable = True
+version = "1.0.0"
+className = "ProjectFlaskPlugin"
+packageName = "ProjectFlask"
+shortDescription = "Project support for Flask projects."
+longDescription = (
+    """This plugin implements project support for Flask projects."""
+)
+needsRestart = False
+pyqtApi = 2
+# End-of-Header
+
+error = ""
+
+flaskPluginObject = None
+
+
+def apiFiles(language):
+    """
+    Module function to return the API files made available by this plugin.
+    
+    @param language language to get APIs for (string)
+    @return list of API filenames (list of string)
+    """
+    if language in ["Python3"]:
+        apisDir = os.path.join(os.path.dirname(__file__),
+                               "ProjectFlask", "APIs")
+        apis = glob.glob(os.path.join(apisDir, '*.api'))
+    else:
+        apis = []
+    return apis
+
+
+def createFlaskPage(configDlg):
+    """
+    Module function to create the Flask configuration page.
+    
+    @param configDlg reference to the configuration dialog
+    @return reference to the configuration page
+    """
+    global flaskPluginObject
+    from ProjectFlask.ConfigurationPage.FlaskPage import FlaskPage
+    page = FlaskPage(flaskPluginObject)
+    return page
+
+
+def getConfigData():
+    """
+    Module function returning data as required by the configuration dialog.
+    
+    @return dictionary containing the relevant data
+    @rtype dict
+    """
+    try:
+        usesDarkPalette = e5App().usesDarkPalette()
+    except AttributeError:
+        from PyQt5.QtGui import QPalette
+        palette = e5App().palette()
+        lightness = palette.color(QPalette.Window).lightness()
+        usesDarkPalette = lightness <= 128
+    if usesDarkPalette:
+        iconSuffix = "dark"
+    else:
+        iconSuffix = "light"
+    
+    return {
+        "flaskPage": [
+            QCoreApplication.translate("ProjectFlaskPlugin", "Flask"),
+            os.path.join("ProjectFlask", "icons",
+                         "flask-{0}".format(iconSuffix)),
+            createFlaskPage, None, None],
+    }
+
+
+def prepareUninstall():
+    """
+    Module function to prepare for an uninstallation.
+    """
+    Preferences.removeProjectBrowserFlags(ProjectFlaskPlugin.PreferencesKey)
+    Preferences.Prefs.settings.remove(ProjectFlaskPlugin.PreferencesKey)
+    Preferences.Prefs.rsettings.remove(ProjectFlaskPlugin.PreferencesKey)
+
+
+class ProjectFlaskPlugin(QObject):
+    """
+    Class implementing the Flask project plugin.
+    """
+    PreferencesKey = "Flask"
+    
+    lexerAssociations = {
+        "*.htm": "Pygments|HTML+Django/Jinja",
+        "*.html": "Pygments|HTML+Django/Jinja",
+    }
+    
+    def __init__(self, ui):
+        """
+        Constructor
+        
+        @param ui reference to the user interface object
+        @type UI.UserInterface
+        """
+        QObject.__init__(self, ui)
+        self.__ui = ui
+        self.__initialize()
+        
+        self.__defaults = {
+            "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["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.__translator = None
+        self.__loadTranslator()
+    
+    def __initialize(self):
+        """
+        Private slot to (re)initialize the plugin.
+        """
+        self.__object = None
+        
+        self.__mainMenu = None
+        self.__mainAct = None
+        self.__separatorAct = None
+        
+        self.__e5project = e5App().getObject("Project")
+        
+        self.__supportedVariants = []
+    
+    def activate(self):
+        """
+        Public method to activate this plugin.
+        
+        @return tuple of None and activation status
+        @rtype bool
+        """
+        global flaskPluginObject
+        flaskPluginObject = self
+        
+        try:
+            usesDarkPalette = e5App().usesDarkPalette()
+        except AttributeError:
+            from PyQt5.QtGui import QPalette
+            palette = e5App().palette()
+            lightness = palette.color(QPalette.Window).lightness()
+            usesDarkPalette = lightness <= 128
+        if usesDarkPalette:
+            iconSuffix = "dark"
+        else:
+            iconSuffix = "light"
+        
+        self.__object = Project(self, iconSuffix, self.__ui)
+        self.__object.initActions()
+        e5App().registerPluginObject("ProjectFlask", self.__object)
+        
+        self.__mainMenu = self.__object.initMenu()
+        
+        self.__supportedVariants = self.__object.supportedPythonVariants()
+        
+        if self.__supportedVariants:
+            self.__e5project.registerProjectType(
+                "Flask", self.tr("Flask"),
+                self.fileTypesCallback,
+                lexerAssociationCallback=self.lexerAssociationCallback,
+#                binaryTranslationsCallback=self.binaryTranslationsCallback,
+                progLanguages=self.__supportedVariants[:])
+        
+        from Project.ProjectBrowser import (
+            SourcesBrowserFlag, FormsBrowserFlag, TranslationsBrowserFlag,
+            OthersBrowserFlag
+        )
+        Preferences.setProjectBrowserFlagsDefault(
+            "Flask",
+            SourcesBrowserFlag | FormsBrowserFlag |
+            TranslationsBrowserFlag | OthersBrowserFlag,
+        )
+        
+        if self.__e5project.isOpen():
+            self.__projectOpened()
+#            self.__object.projectOpenedHooks()
+        
+        e5App().getObject("Project").projectOpened.connect(
+            self.__projectOpened)
+        e5App().getObject("Project").projectClosed.connect(
+            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)
+        
+        return None, True
+    
+    def deactivate(self):
+        """
+        Public method to deactivate this plugin.
+        """
+        e5App().unregisterPluginObject("ProjectFlask")
+        
+        e5App().getObject("Project").projectOpened.disconnect(
+            self.__projectOpened)
+        e5App().getObject("Project").projectClosed.disconnect(
+            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)
+        
+        self.__e5project.unregisterProjectType("Flask")
+        
+#        self.__object.projectClosedHooks()
+#        self.__projectClosed()
+        
+        self.__initialize()
+    
+    def __loadTranslator(self):
+        """
+        Private method to load the translation file.
+        """
+        if self.__ui is not None:
+            loc = self.__ui.getLocale()
+            if loc and loc != "C":
+                locale_dir = os.path.join(
+                    os.path.dirname(__file__), "ProjectFlask", "i18n")
+                translation = "flask_{0}".format(loc)
+                translator = QTranslator(None)
+                loaded = translator.load(translation, locale_dir)
+                if loaded:
+                    self.__translator = translator
+                    e5App().installTranslator(self.__translator)
+                else:
+                    print("Warning: translation file '{0}' could not be"
+                          " loaded.".format(translation))
+                    print("Using default.")
+    
+    def __projectOpened(self):
+        """
+        Private slot to handle the projectOpened signal.
+        """
+        if self.__e5project.getProjectType() == "Flask":
+            projectToolsMenu = self.__ui.getMenu("project_tools")
+            if projectToolsMenu is not None:
+                insertBeforeAct = projectToolsMenu.actions()[0]
+                self.__mainAct = projectToolsMenu.insertMenu(
+                    insertBeforeAct, self.__mainMenu)
+                self.__separatorAct = projectToolsMenu.insertSeparator(
+                    insertBeforeAct)
+            else:
+                projectAct = self.__ui.getMenuBarAction("project")
+                actions = self.__ui.menuBar().actions()
+                insertBeforeAct = actions[actions.index(projectAct) + 1]
+                self.__mainAct = self.__ui.menuBar().insertMenu(
+                    insertBeforeAct, self.__mainMenu)
+    
+    def __projectClosed(self):
+        """
+        Private slot to handle the projectClosed signal.
+        """
+        if self.__mainAct is not None:
+            projectToolsMenu = self.__ui.getMenu("project_tools")
+            if projectToolsMenu is not None:
+                projectToolsMenu.removeAction(self.__separatorAct)
+                projectToolsMenu.removeAction(self.__mainAct)
+                self.__mainAct = None
+                self.__separatorAct = None
+            else:
+                self.__ui.menuBar().removeAction(self.__mainAct)
+                self.__mainAct = None
+            self.__object.projectClosed()
+    
+    def fileTypesCallback(self):
+        """
+        Public method get the filetype associations of the Django project type.
+        
+        @return dictionary with file type associations
+        @rtype dict
+        """
+        if self.__e5project.getProjectType() == "Flask":
+            fileTypes = {
+                "*.py": "SOURCES",
+                "*.js": "SOURCES",
+                "*.html": "FORMS",
+                "*.htm": "FORMS",
+                "*.pot": "TRANSLATIONS",
+                "*.po": "TRANSLATIONS",
+                "*.mo": "TRANSLATIONS",
+            }
+        else:
+            fileTypes = {}
+        return fileTypes
+    
+    def lexerAssociationCallback(self, filename):
+        """
+        Public method to get the lexer association of the Django project type
+        for a file.
+        
+        @param filename name of the file
+        @type str
+        @return name of the lexer (Pygments lexers are prefixed with
+            'Pygments|')
+        @rtype str
+        """
+        for pattern, language in self.lexerAssociations.items():
+            if fnmatch.fnmatch(filename, pattern):
+                return language
+        
+        return ""
+    
+    def getDefaultPreference(self, key):
+        """
+        Public method to get the default value for a setting.
+        
+        @param key the key of the value to get
+        @type str
+        @return the requested setting
+        @rtype any
+        """
+        return self.__defaults[key]
+    
+    def getPreferences(self, key):
+        """
+        Public method to retrieve the various settings.
+        
+        @param key the key of the value to get
+        @type str
+        @return the requested setting
+        @rtype any
+        """
+        if key in ["UseExternalBrowser"]:
+            return Preferences.toBool(Preferences.Prefs.settings.value(
+                self.PreferencesKey + "/" + key, self.__defaults[key]))
+        else:
+            return Preferences.Prefs.settings.value(
+                self.PreferencesKey + "/" + key, self.__defaults[key])
+    
+    def setPreferences(self, key, value):
+        """
+        Public method to store the various settings.
+        
+        @param key the key of the setting to be set
+        @type str
+        @param value the value to be set
+        @type any
+        """
+        Preferences.Prefs.settings.setValue(
+            self.PreferencesKey + "/" + key, value)
+        
+        if key in ["VirtualEnvironmentNamePy3"]:
+            self.__reregisterProjectType()
+    
+    def __reregisterProjectType(self):
+        """
+        Private method to re-register the project type.
+        """
+        supportedVariants = self.__object.supportedPythonVariants()
+        if supportedVariants != self.__supportedVariants:
+            # step 1: unregister
+            self.__e5project.unregisterProjectType("Flask")
+            
+            # step 2: register again with new language settings
+            self.__supportedVariants = supportedVariants
+            if self.__supportedVariants:
+                self.__e5project.registerProjectType(
+                    "Flask",
+                    self.tr("Pyramid"), self.fileTypesCallback,
+                    lexerAssociationCallback=self.lexerAssociationCallback,
+#                    binaryTranslationsCallback=self.binaryTranslationsCallback,
+                    progLanguages=self.__supportedVariants[:])
+    
+    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 self.__object is not None:
+            return self.__object.getMenu(name)
+        else:
+            return None
+    
+    def getMenuNames(self):
+        """
+        Public method to get the names of all menus.
+        
+        @return menu names
+        @rtype list of str
+        """
+        if self.__object is not None:
+            return list(self.__menus.keys())
+        else:
+            return []
+
+#
+# eflag: noqa = M801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/ConfigurationPage/FlaskPage.py	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Flask configuration page.
+"""
+
+from PyQt5.QtCore import pyqtSlot
+
+from E5Gui.E5Application import e5App
+
+from Preferences.ConfigurationPages.ConfigurationPageBase import (
+    ConfigurationPageBase
+)
+from .Ui_FlaskPage import Ui_FlaskPage
+
+import UI.PixmapCache
+
+
+class FlaskPage(ConfigurationPageBase, Ui_FlaskPage):
+    """
+    Class implementing the Flask configuration page.
+    """
+    def __init__(self, plugin):
+        """
+        Constructor
+        
+        @param plugin reference to the plugin object
+        """
+        super(FlaskPage, self).__init__()
+        self.setupUi(self)
+        self.setObjectName("FlaskPage")
+        
+        self.__plugin = plugin
+        
+        self.urlResetButton.setIcon(
+            UI.PixmapCache.getIcon("editUndo"))
+        self.py3VenvNamesReloadButton.setIcon(
+            UI.PixmapCache.getIcon("reload"))
+        
+        venvManager = e5App().getObject("VirtualEnvManager")
+        self.py3VenvNameComboBox.addItems(
+            [""] + sorted(venvManager.getVirtualenvNames()))
+        
+        # set initial values
+        self.externalBrowserCheckBox.setChecked(
+            self.__plugin.getPreferences("UseExternalBrowser"))
+        
+        venvName = self.__plugin.getPreferences(
+            "VirtualEnvironmentNamePy3")
+        if venvName:
+            index = self.py3VenvNameComboBox.findText(venvName)
+            if index < 0:
+                index = 0
+            self.py3VenvNameComboBox.setCurrentIndex(index)
+        
+        self.urlEdit.setText(
+            self.__plugin.getPreferences("FlaskDocUrl"))
+    
+    def save(self):
+        """
+        Public slot to save the Flask configuration.
+        """
+        self.__plugin.setPreferences(
+            "UseExternalBrowser", self.externalBrowserCheckBox.isChecked())
+        
+        self.__plugin.setPreferences(
+            "VirtualEnvironmentNamePy3",
+            self.py3VenvNameComboBox.currentText())
+        
+        self.__plugin.setPreferences(
+            "FlaskDocUrl", self.urlEdit.text())
+    
+    @pyqtSlot()
+    def on_py3VenvNamesReloadButton_clicked(self):
+        """
+        Private slot to reload the virtual environment names.
+        """
+        currentVenvName = self.py3VenvNameComboBox.currentText()
+        self.py3VenvNameComboBox.clear()
+        venvManager = e5App().getObject("VirtualEnvManager")
+        self.py3VenvNameComboBox.addItems(
+            [""] + sorted(venvManager.getVirtualenvNames()))
+        if currentVenvName:
+            index = self.py3VenvNameComboBox.findText(currentVenvName)
+            if index < 0:
+                index = 0
+            self.py3VenvNameComboBox.setCurrentIndex(index)
+    
+    @pyqtSlot()
+    def on_urlResetButton_clicked(self):
+        """
+        Private slot to reset the Flask documentation URL.
+        """
+        self.urlEdit.setText(
+            self.__plugin.getDefaultPreference("FlaskDocUrl"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/ConfigurationPage/FlaskPage.ui	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FlaskPage</class>
+ <widget class="QWidget" name="FlaskPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>373</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="headerLabel">
+     <property name="text">
+      <string>&lt;b&gt;Configure Flask&lt;/b&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line15">
+     <property name="frameShape">
+      <enum>QFrame::HLine</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="flaskBrowserGroup">
+     <property name="title">
+      <string>Web-Browser</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_5">
+      <item>
+       <widget class="QCheckBox" name="externalBrowserCheckBox">
+        <property name="toolTip">
+         <string>Select to use an external web-browser</string>
+        </property>
+        <property name="text">
+         <string>Use external web-browser</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="flaskVirtualEnvironmentPy3Group">
+     <property name="title">
+      <string>Flak Virtual Environment</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QLabel" name="label_13">
+        <property name="text">
+         <string>Select the Virtual Environment to be used with Flask</string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QComboBox" name="py3VenvNameComboBox"/>
+        </item>
+        <item>
+         <widget class="QToolButton" name="py3VenvNamesReloadButton">
+          <property name="toolTip">
+           <string>Press to reload the names of the virtual environments</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="flaskDocumentationGroup">
+     <property name="title">
+      <string>Pyramid Documentation</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QLabel" name="label_6">
+        <property name="text">
+         <string>URL:</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="urlEdit">
+        <property name="toolTip">
+         <string>Enter the URL of the Flask documentation</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QToolButton" name="urlResetButton">
+        <property name="toolTip">
+         <string>Press to reset the URL to the default URL</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>64</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/ConfigurationPage/__init__.py	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the Flask page of the configuration dialog.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/Project.py	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Flask project support.
+"""
+
+import os
+
+from PyQt5.QtCore import pyqtSlot, QObject, QProcess, QTimer
+from PyQt5.QtWidgets import QMenu
+
+from E5Gui import E5MessageBox
+from E5Gui.E5Action import E5Action
+from E5Gui.E5Application import e5App
+
+from Globals import isWindowsPlatform
+
+import UI.PixmapCache
+import Utilities
+
+
+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(Project, self).__init__(parent)
+        
+        self.__plugin = plugin
+        self.__iconSuffix = iconSuffix
+        self.__ui = parent
+
+        self.__e5project = e5App().getObject("Project")
+        self.__virtualEnvManager = e5App().getObject("VirtualEnvManager")
+        
+        self.__menus = {}   # dictionary with references to menus
+         
+        self.__serverProc = None
+       
+        self.__flaskVersions = {
+            "python": "",
+            "flask": "",
+            "werkzeug": "",
+        }
+    
+    def initActions(self):
+        """
+        Public method to define the Flask actions.
+        """
+        self.actions = []
+    
+        ##############################
+        ## about action below       ##
+        ##############################
+        
+        self.aboutFlaskAct = E5Action(
+            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)
+    
+    def initMenu(self):
+        """
+        Public method to initialize the Flask menu.
+        
+        @return the menu generated
+        @rtype QMenu
+        """
+        self.__menus = {}   # clear menus references
+        
+        menu = QMenu(self.tr('&Flask'), self.__ui)
+        menu.setTearOffEnabled(True)
+        
+        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())
+    
+    ##################################################################
+    ## slots below implement general functionality
+    ##################################################################
+    
+    def projectClosed(self):
+        """
+        Public method to handle the closing of a project.
+        """
+        if self.__serverProc is not None:
+            self.__serverProcFinished()
+    
+    # TODO: implement this correctly
+    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 (IOError, 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 __getVirtualEnvironment(self):
+        """
+        Private method to get the path of the virtual environment.
+        
+        @return path of the virtual environment
+        @rtype str
+        """
+        language = self.__e5project.getProjectLanguage()
+        if language == "Python3":
+            venvName = self.__plugin.getPreferences(
+                "VirtualEnvironmentNamePy3")
+        else:
+            venvName = ""
+        if venvName:
+            virtEnv = self.__virtualEnvManager.getVirtualenvDirectory(
+                venvName)
+        else:
+            virtEnv = ""
+        
+        if virtEnv and not os.path.exists(virtEnv):
+            virtEnv = ""
+        
+        return virtEnv      # __IGNORE_WARNING_M834__
+    
+    def getFlaskCommand(self):
+        """
+        Public method to build the Flask command.
+        
+        @return full flask command
+        @rtype str
+        """
+        cmd = "flask"
+        
+        virtualEnv = self.__getVirtualEnvironment()
+        if isWindowsPlatform():
+            fullCmds = [
+                os.path.join(virtualEnv, "Scripts", cmd + '.exe'),
+                os.path.join(virtualEnv, "bin", cmd + '.exe'),
+                cmd     # fall back to just cmd
+            ]
+            for cmd in fullCmds:
+                if os.path.exists(cmd):
+                    break
+        else:
+            fullCmds = [
+                os.path.join(virtualEnv, "bin", cmd),
+                os.path.join(virtualEnv, "local", "bin", cmd),
+                Utilities.getExecutablePath(cmd),
+                cmd     # fall back to just cmd
+            ]
+            for cmd in fullCmds:
+                if os.path.exists(cmd):
+                    break
+        return cmd
+    
+    @pyqtSlot()
+    def __flaskInfo(self):
+        """
+        Private slot to show some info about Flask.
+        """
+        versions = self.getFlaskVersionStrings()
+        url = "https://flask.palletsprojects.com"
+        
+        msgBox = E5MessageBox.E5MessageBox(
+            E5MessageBox.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}\">"
+                "{3}</a></td></tr>"
+                "</table></p>"
+            ).format(versions["flask"], versions["werkzeug"],
+                     versions["python"], url),
+            modal=True,
+            buttons=E5MessageBox.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
+    
+    ##################################################################
+    ## slots below implement run functions
+    ##################################################################
+    
+    def __runServer(self, logging=False):
+        """
+        Private slot to start the Pyramid Web server.
+        
+        @param logging flag indicating to enable logging
+        @type bool
+        """
+        # TODO: implement this
+    
+    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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/__init__.py	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing project support for eric6 Flask projects.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/icons/flask-dark.svg	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="flask-dark.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient2322"
+       osb:paint="solid">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2320" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2824"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2840"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2878"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2894"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2926"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2976"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3020"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3036"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3052"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3866"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="24.577155"
+     inkscape:cx="26.929966"
+     inkscape:cy="9.5499179"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1377"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     fit-margin-top="10"
+     fit-margin-left="10"
+     fit-margin-right="10"
+     fit-margin-bottom="10" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-29.820801,-188.71498)">
+    <path
+       style="fill:#eff0f1;stroke:#232629;stroke-width:0.20519325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 30.923396,195.74806 c 3.145523,-1.0064 2.468667,-0.9463 6.110325,-1.58477 0.871154,0.2475 2.349849,0.58367 2.28106,1.26197 -0.640202,0.23817 -1.574558,-0.66251 -2.077416,-0.15803 0.001,1.8906 0.611821,3.92047 1.38264,5.7213 0.770819,1.80084 1.936492,3.37264 3.516943,4.34706 2.094978,1.30012 4.299555,1.53282 6.528075,1.6067 0.68396,0.0244 1.775303,-0.15172 2.053185,0 -0.503838,0.89318 -1.977451,1.31486 -3.509599,1.50982 -1.532147,0.19497 -3.122831,0.16323 -3.860809,0.14956 -1.62967,-0.0513 -3.035316,-0.27209 -4.314541,-0.74821 -3.300834,-1.33007 -5.168705,-4.00553 -6.580061,-6.92574 -0.810575,-1.70004 -1.372105,-3.48921 -1.529802,-5.17966 z"
+       id="path3826"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccscccscccc" />
+    <path
+       style="fill:#eff0f1;stroke:#232629;stroke-width:0.20519325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 32.409908,191.27103 c -0.04381,0.0897 -0.03947,0.20507 -0.0029,0.2979 0.08019,0.2036 0.287169,0.33376 0.451026,0.48043 0.07471,0.0668 0.232755,0.0825 0.242494,0.18156 0.02208,0.22478 -0.277003,0.36603 -0.457053,0.50508 -0.135007,0.10429 -0.39442,0.0797 -0.456615,0.23752 -0.04282,0.10864 0.08588,0.21557 0.119784,0.32724 0.04778,0.15736 0.168456,0.32332 0.117539,0.4797 -0.06128,0.1882 -0.276932,0.29214 -0.4534,0.38531 -0.194166,0.10251 -0.456466,0.0511 -0.636685,0.17601 -0.171707,0.11903 -0.357539,0.29835 -0.364548,0.50551 -0.0032,0.094 -0.03944,0.27471 -0.02398,0.31958 1.319307,-0.42649 2.071305,-0.6679 2.943016,-0.88241 0.87171,-0.21451 1.652197,-0.34889 3.033831,-0.58391 -0.007,-0.0469 -0.122777,-0.19045 -0.169009,-0.27224 -0.101974,-0.18042 -0.349628,-0.2534 -0.556884,-0.27945 -0.217528,-0.0273 -0.426311,0.13965 -0.645844,0.13863 -0.199523,-9.2e-4 -0.438768,0.007 -0.580097,-0.13175 -0.117432,-0.11508 -0.08715,-0.31808 -0.117539,-0.47971 -0.02156,-0.1147 0.04311,-0.26907 -0.04508,-0.34557 -0.128078,-0.11109 -0.346725,0.0307 -0.514621,7.7e-4 -0.223911,-0.0399 -0.554373,-0.0267 -0.638703,-0.23625 -0.0372,-0.0924 0.09574,-0.17934 0.131059,-0.27321 0.07747,-0.20588 0.200808,-0.41708 0.177788,-0.6347 -0.0105,-0.0993 -0.05998,-0.20352 -0.14029,-0.26277 -0.172516,-0.12226 -0.447889,-0.13586 -0.772479,-0.0539 -0.324587,0.0819 -0.547919,0.21939 -0.640796,0.4007 z"
+       id="path3832"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csssssscssscccssscssssssccc" />
+    <path
+       style="fill:none;stroke:#232629;stroke-width:0.20519325;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 37.291027,195.21739 c 0,0 -2.518612,0.54167 -3.683949,0.87768 -0.826288,0.23825 -2.438425,0.84257 -2.438425,0.84257"
+       id="path3847"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csc" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/icons/flask-light.svg	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="flask-light.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient2322"
+       osb:paint="solid">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2320" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2824"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2840"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2878"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2894"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2926"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2976"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3020"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3036"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3052"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3866"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="24.577155"
+     inkscape:cx="18.873704"
+     inkscape:cy="9.5499179"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1377"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     fit-margin-top="10"
+     fit-margin-left="10"
+     fit-margin-right="10"
+     fit-margin-bottom="10" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-29.820801,-188.71498)">
+    <path
+       style="fill:#232629;stroke:#eff0f1;stroke-width:0.20519325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 30.923396,195.74806 c 3.145523,-1.0064 2.468667,-0.9463 6.110325,-1.58477 0.871154,0.2475 2.349849,0.58367 2.28106,1.26197 -0.640202,0.23817 -1.574558,-0.66251 -2.077416,-0.15803 0.001,1.8906 0.611821,3.92047 1.38264,5.7213 0.770819,1.80084 1.936492,3.37264 3.516943,4.34706 2.094978,1.30012 4.299555,1.53282 6.528075,1.6067 0.68396,0.0244 1.775303,-0.15172 2.053185,0 -0.503838,0.89318 -1.977451,1.31486 -3.509599,1.50982 -1.532147,0.19497 -3.122831,0.16323 -3.860809,0.14956 -1.62967,-0.0513 -3.035316,-0.27209 -4.314541,-0.74821 -3.300834,-1.33007 -5.168705,-4.00553 -6.580061,-6.92574 -0.810575,-1.70004 -1.372105,-3.48921 -1.529802,-5.17966 z"
+       id="path3826"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccscccscccc" />
+    <path
+       style="fill:#232629;stroke:#eff0f1;stroke-width:0.20519325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 32.409908,191.27103 c -0.04381,0.0897 -0.03947,0.20507 -0.0029,0.2979 0.08019,0.2036 0.287169,0.33376 0.451026,0.48043 0.07471,0.0668 0.232755,0.0825 0.242494,0.18156 0.02208,0.22478 -0.277003,0.36603 -0.457053,0.50508 -0.135007,0.10429 -0.39442,0.0797 -0.456615,0.23752 -0.04282,0.10864 0.08588,0.21557 0.119784,0.32724 0.04778,0.15736 0.168456,0.32332 0.117539,0.4797 -0.06128,0.1882 -0.276932,0.29214 -0.4534,0.38531 -0.194166,0.10251 -0.456466,0.0511 -0.636685,0.17601 -0.171707,0.11903 -0.357539,0.29835 -0.364548,0.50551 -0.0032,0.094 -0.03944,0.27471 -0.02398,0.31958 1.319307,-0.42649 2.071305,-0.6679 2.943016,-0.88241 0.87171,-0.21451 1.652197,-0.34889 3.033831,-0.58391 -0.007,-0.0469 -0.122777,-0.19045 -0.169009,-0.27224 -0.101974,-0.18042 -0.349628,-0.2534 -0.556884,-0.27945 -0.217528,-0.0273 -0.426311,0.13965 -0.645844,0.13863 -0.199523,-9.2e-4 -0.438768,0.007 -0.580097,-0.13175 -0.117432,-0.11508 -0.08715,-0.31808 -0.117539,-0.47971 -0.02156,-0.1147 0.04311,-0.26907 -0.04508,-0.34557 -0.128078,-0.11109 -0.346725,0.0307 -0.514621,7.7e-4 -0.223911,-0.0399 -0.554373,-0.0267 -0.638703,-0.23625 -0.0372,-0.0924 0.09574,-0.17934 0.131059,-0.27321 0.07747,-0.20588 0.200808,-0.41708 0.177788,-0.6347 -0.0105,-0.0993 -0.05998,-0.20352 -0.14029,-0.26277 -0.172516,-0.12226 -0.447889,-0.13586 -0.772479,-0.0539 -0.324587,0.0819 -0.547919,0.21939 -0.640796,0.4007 z"
+       id="path3832"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csssssscssscccssscssssssccc" />
+    <path
+       style="fill:none;stroke:#eff0f1;stroke-width:0.20519325;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+       d="m 37.291027,195.21739 c 0,0 -2.518612,0.54167 -3.683949,0.87768 -0.826288,0.23825 -2.438425,0.84257 -2.438425,0.84257"
+       id="path3847"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csc" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/icons/flask64-dark.svg	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="64"
+   height="64"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="flask64-dark.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient2322"
+       osb:paint="solid">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2320" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2824"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2840"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2878"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2894"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2926"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2976"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3020"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3036"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3052"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3866"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="12.288577"
+     inkscape:cx="3.3305988"
+     inkscape:cy="22.365175"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1377"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     fit-margin-top="10"
+     fit-margin-left="10"
+     fit-margin-right="10"
+     fit-margin-bottom="10" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-29.820801,-146.71498)">
+    <path
+       style="fill:#eff0f1;fill-opacity:1;stroke:#232629;stroke-width:0.6155774;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 32.128588,166.81452 c 9.436568,-3.01919 7.406001,-2.83889 18.330974,-4.75427 2.613463,0.74249 7.049548,1.75098 6.843181,3.78587 -1.920606,0.7145 -4.723674,-1.98751 -6.232249,-0.47409 0.003,5.67175 1.835464,11.76133 4.147921,17.16377 2.312457,5.40248 5.809476,10.11785 10.550829,13.04109 6.284933,3.90033 12.898666,4.59842 19.584225,4.82007 2.05188,0.0732 5.325909,-0.45517 6.159555,0 -1.511514,2.67951 -5.932353,3.94455 -10.528796,4.52942 -4.596443,0.5849 -9.368494,0.48969 -11.582429,0.44867 -4.88901,-0.1539 -9.105947,-0.81626 -12.943622,-2.24461 -9.902503,-3.99017 -15.506116,-12.01651 -19.740183,-20.77707 -2.431725,-5.10008 -4.116315,-10.46754 -4.589406,-15.53885 z"
+       id="path3826"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccscccscccc" />
+    <path
+       style="fill:#eff0f1;fill-opacity:1;stroke:#232629;stroke-width:0.6155774;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 36.588124,153.38352 c -0.13143,0.2691 -0.11841,0.61521 -0.0087,0.8937 0.24057,0.6108 0.861507,1.00127 1.353078,1.44128 0.22413,0.20039 0.698265,0.24749 0.727482,0.54467 0.06624,0.67434 -0.831009,1.09808 -1.371159,1.51523 -0.405021,0.31287 -1.18326,0.2391 -1.369845,0.71255 -0.12846,0.32592 0.25764,0.64672 0.359352,0.98172 0.14334,0.47207 0.505368,0.96995 0.352617,1.43908 -0.18384,0.5646 -0.830796,0.87642 -1.3602,1.15593 -0.582498,0.30752 -1.369398,0.15329 -1.910055,0.52802 -0.515121,0.35709 -1.072616,0.89504 -1.093644,1.51653 -0.0096,0.282 -0.11832,0.82412 -0.07194,0.95872 3.95792,-1.27946 6.213915,-2.00368 8.829047,-2.64721 2.61513,-0.64352 4.956591,-1.04666 9.101492,-1.75171 -0.021,-0.1407 -0.36833,-0.57135 -0.507026,-0.81672 -0.305922,-0.54124 -1.048884,-0.76019 -1.670652,-0.83834 -0.652584,-0.0819 -1.278933,0.41895 -1.937532,0.4159 -0.598569,-0.003 -1.316304,0.021 -1.740291,-0.39525 -0.352296,-0.34525 -0.26145,-0.95424 -0.352617,-1.43912 -0.06468,-0.3441 0.12933,-0.80721 -0.13524,-1.03671 -0.384234,-0.33327 -1.040175,0.0921 -1.543863,0.002 -0.671733,-0.1197 -1.663119,-0.0801 -1.916109,-0.70875 -0.1116,-0.2772 0.28722,-0.53802 0.393177,-0.81963 0.23241,-0.61763 0.602424,-1.25123 0.533364,-1.90408 -0.0315,-0.2979 -0.17994,-0.61056 -0.42087,-0.78831 -0.517548,-0.36677 -1.343667,-0.40757 -2.317437,-0.1617 -0.973761,0.2457 -1.643757,0.65817 -1.922388,1.20209 z"
+       id="path3832"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csssssscssscccssscssssssccc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.6155774;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 51.23148,165.22252 c 0,0 -7.555836,1.625 -11.051846,2.63301 -2.478864,0.71474 -7.315275,2.5277 -7.315275,2.5277"
+       id="path3847"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csc" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/icons/flask64-light.svg	Sun Nov 08 17:54:22 2020 +0100
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="64"
+   height="64"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
+   sodipodi:docname="flask64-light.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient2322"
+       osb:paint="solid">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop2320" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2824"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2840"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2878"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2894"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2926"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2976"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3020"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3036"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3052"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3866"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.6893365"
+     inkscape:cx="1.3620805"
+     inkscape:cy="41.288557"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1377"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     fit-margin-top="10"
+     fit-margin-left="10"
+     fit-margin-right="10"
+     fit-margin-bottom="10" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-29.820801,-146.71498)">
+    <path
+       style="fill:#232629;fill-opacity:1;stroke:#eff0f1;stroke-width:0.61557972;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 32.128589,166.81422 c 9.436568,-3.0192 7.406001,-2.8389 18.330974,-4.75431 2.613462,0.7425 7.049547,1.75101 6.84318,3.78591 -1.920606,0.71451 -4.723674,-1.98753 -6.232248,-0.47409 0.003,5.6718 1.835463,11.76141 4.14792,17.1639 2.312457,5.40252 5.809476,10.11792 10.550829,13.04118 6.284933,3.90036 12.898664,4.59846 19.584224,4.8201 2.05188,0.0732 5.325909,-0.45516 6.159555,0 -1.511514,2.67954 -5.932353,3.94458 -10.528797,4.52946 -4.596441,0.58491 -9.368493,0.48969 -11.582427,0.44868 -4.889009,-0.1539 -9.105947,-0.81627 -12.943622,-2.24463 C 46.555675,199.14021 40.952062,191.11383 36.717995,182.3532 34.28627,177.25308 32.60168,171.88557 32.128589,166.81422 Z"
+       id="path3826"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccscccscccc" />
+    <path
+       style="fill:#232629;fill-opacity:1;stroke:#eff0f1;stroke-width:0.61557972;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 36.588125,153.38313 c -0.13143,0.2691 -0.11841,0.61521 -0.0087,0.8937 0.24057,0.6108 0.861507,1.00128 1.353078,1.44129 0.22413,0.2004 0.698265,0.2475 0.727482,0.54468 0.06624,0.67434 -0.831009,1.09809 -1.371159,1.51524 -0.405021,0.31287 -1.18326,0.2391 -1.369845,0.71256 -0.12846,0.32592 0.25764,0.64671 0.359352,0.98172 0.14334,0.47208 0.505368,0.96996 0.352617,1.4391 -0.18384,0.5646 -0.830796,0.87642 -1.3602,1.15593 -0.582498,0.30753 -1.369398,0.1533 -1.910055,0.52803 -0.515121,0.35709 -1.072617,0.89505 -1.093644,1.51653 -0.0096,0.282 -0.11832,0.82413 -0.07194,0.95874 3.957921,-1.27947 6.213915,-2.0037 8.829047,-2.64723 2.61513,-0.64353 4.956591,-1.04667 9.101493,-1.75173 -0.021,-0.1407 -0.368331,-0.57135 -0.507027,-0.81672 -0.305922,-0.54126 -1.048884,-0.7602 -1.670652,-0.83835 -0.652584,-0.0819 -1.278933,0.41895 -1.937532,0.41589 -0.598569,-0.003 -1.316304,0.021 -1.740291,-0.39525 -0.352296,-0.34524 -0.26145,-0.95424 -0.352617,-1.43913 -0.06468,-0.3441 0.12933,-0.80721 -0.13524,-1.03671 -0.384234,-0.33327 -1.040175,0.0921 -1.543863,0.002 -0.671733,-0.1197 -1.663119,-0.0801 -1.916109,-0.70875 -0.1116,-0.2772 0.28722,-0.53802 0.393177,-0.81963 0.23241,-0.61764 0.602424,-1.25124 0.533364,-1.9041 -0.0315,-0.2979 -0.17994,-0.61056 -0.42087,-0.78831 -0.517548,-0.36678 -1.343666,-0.40758 -2.317436,-0.1617 -0.973761,0.2457 -1.643757,0.65817 -1.922388,1.2021 z"
+       id="path3832"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csssssscssscccssscssssssccc" />
+    <path
+       style="fill:none;fill-opacity:1;stroke:#eff0f1;stroke-width:0.61557972;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 51.231481,165.22221 c 0,0 -7.555836,1.62501 -11.051847,2.63304 -2.478863,0.71475 -7.315274,2.52771 -7.315274,2.52771"
+       id="path3847"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="csc" />
+  </g>
+</svg>

eric ide

mercurial