Added the 'Web' project type and the HTML5 to CSS3 converter.

Wed, 31 Dec 2014 19:14:36 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 31 Dec 2014 19:14:36 +0100
changeset 1
e3a92a671aa5
parent 0
9099ee404178
child 2
917f93fc61dd

Added the 'Web' project type and the HTML5 to CSS3 converter.

.hgignore file | annotate | diff | comparison | revisions
PluginProjectWeb file | annotate | diff | comparison | revisions
PluginProjectWeb.py file | annotate | diff | comparison | revisions
PluginWeb.e4p file | annotate | diff | comparison | revisions
ProjectWeb/Html5ToCss3Converter.py file | annotate | diff | comparison | revisions
ProjectWeb/Html5ToCss3ConverterParameterDialog.py file | annotate | diff | comparison | revisions
ProjectWeb/Html5ToCss3ConverterParameterDialog.ui file | annotate | diff | comparison | revisions
ProjectWeb/__init__.py file | annotate | diff | comparison | revisions
--- a/.hgignore	Tue Dec 30 14:58:24 2014 +0100
+++ b/.hgignore	Wed Dec 31 19:14:36 2014 +0100
@@ -17,3 +17,4 @@
 glob:tmp
 glob:__pycache__
 glob:**.DS_Store
+glob:Ui_*.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PluginProjectWeb.py	Wed Dec 31 19:14:36 2014 +0100
@@ -0,0 +1,271 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Web project plugin.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import QObject, QTranslator
+from PyQt5.QtWidgets import QMenu
+
+from E5Gui.E5Application import e5App
+
+import Preferences
+
+try:
+    from bs4 import BeautifulSoup   # __IGNORE_EXCEPTION__
+    BeautifulSoupAvailable = True
+except ImportError:
+    BeautifulSoup = None
+    BeautifulSoupAvailable = False
+
+# Start-Of-Header
+name = "PluginProjectWeb"
+author = "Detlev Offenbach <detlev@die-offenbachs.de>"
+autoactivate = True
+deactivateable = True
+version = "0.1.0"
+className = "ProjectWebPlugin"
+packageName = "ProjectWeb"
+shortDescription = "Support for Web projects and web related tools."
+longDescription = (
+    """This plug-in provides support for ordinary web projects and some web"""
+    """ related tools.\n\nIt requires BeautifulSoup4 for some of its"""
+    """ functionality."""
+)
+needsRestart = False
+pyqtApi = 2
+python2Compatible = True
+# End-Of-Header
+
+error = ""
+    
+
+class ProjectWebPlugin(QObject):
+    """
+    Class implementing the Web project plugin.
+    """
+    def __init__(self, ui):
+        """
+        Constructor
+        
+        @param ui reference to the user interface object (UI.UserInterface)
+        """
+        super(ProjectWebPlugin, self).__init__(ui)
+        self.__ui = ui
+        self.__initialize()
+        
+        self.__translator = None
+        self.__loadTranslator()
+        
+        self.__initMenu()
+    
+    def __initialize(self):
+        """
+        Private slot to (re)initialize the plugin.
+        """
+        self.__e5project = e5App().getObject("Project")
+        
+        self.__editors = {}
+    
+    def activate(self):
+        """
+        Public method to activate this plugin.
+        
+        @return tuple of None and activation status (boolean)
+        """
+        global error
+        error = ""     # clear previous error
+        
+        if self.__ui.versionIsNewer('6.0.0', '20141229'):
+            self.__e5project.registerProjectType(
+                "Web", self.tr("Web"),
+                self.fileTypesCallback, progLanguages=["JavaScript"])
+        else:
+            self.__e5project.registerProjectType(
+                "Web", self.tr("Web"),
+                self.fileTypesCallback)
+        
+        from Project.ProjectBrowser import SourcesBrowserFlag, \
+            FormsBrowserFlag, OthersBrowserFlag
+        Preferences.setProjectBrowserFlagsDefault(
+            "Web",
+            SourcesBrowserFlag | FormsBrowserFlag | OthersBrowserFlag,
+        )
+        
+        self.__ui.showMenu.connect(self.__populateMenu)
+        
+        e5App().getObject("ViewManager").editorOpenedEd.connect(
+            self.__editorOpened)
+        e5App().getObject("ViewManager").editorClosedEd.connect(
+            self.__editorClosed)
+        
+        for editor in e5App().getObject("ViewManager").getOpenEditors():
+            self.__editorOpened(editor)
+        
+        return None, True
+    
+    def deactivate(self):
+        """
+        Public method to deactivate this plugin.
+        """
+        self.__e5project.unregisterProjectType("Django")
+        
+        self.__ui.showMenu.disconnect(self.__populateMenu)
+        
+        e5App().getObject("ViewManager").editorOpenedEd.disconnect(
+            self.__editorOpened)
+        e5App().getObject("ViewManager").editorClosedEd.disconnect(
+            self.__editorClosed)
+        
+        for editor, acts in self.__editors.items():
+##            editor.showMenu.disconnect(self.__editorShowMenu)
+            menu = editor.getMenu("Tools")
+            if menu is not None:
+                for act in acts:
+                    menu.removeAction(act)
+        
+        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__), "ProjectWeb", "i18n")
+                translation = "web_%s" % 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 fileTypesCallback(self):
+        """
+        Public method to get the filetype associations of the Web project type.
+        
+        @return dictionary with file type associations
+        """
+        if self.__e5project.getProjectType() == "Web":
+            fileTypes = {
+                "*.html": "FORMS",
+                "*.htm": "FORMS",
+                "*.js": "SOURCES",
+            }
+        else:
+            fileTypes = {}
+        return fileTypes
+    
+    def __initMenu(self):
+        """
+        Private method to initialize the web tools menu.
+        """
+        self.__menu = QMenu(self.tr("Web"))
+        
+        # TODO: add our actions here
+        self.__html5ToCss3Act = self.__menu.addAction(self.tr(
+            "HTML5 to CSS3"), self.__htm5ToCss3)
+        
+        self.__menu.aboutToShow.connect(self.__menuAboutToShow)
+    
+    def __menuAboutToShow(self):
+        """
+        Private slot to prepare the menu before it is shown.
+        """
+        editor = e5App().getObject("ViewManager").activeWindow()
+        selectionAvailable = bool(editor and editor.selectedText() != "")
+        
+        self.__html5ToCss3Act.setEnabled(
+            selectionAvailable and BeautifulSoupAvailable)
+    
+    def __populateMenu(self, name, menu):
+        """
+        Private slot to populate the tools menu with our entries.
+        
+        @param name name of the menu (string)
+        @param menu reference to the menu to be populated (QMenu)
+        """
+        if name != "Tools":
+            return
+        
+##        editor = e5App().getObject("ViewManager").activeWindow()
+##        
+        if not menu.isEmpty():
+            menu.addSeparator()
+        
+        act = menu.addMenu(self.__menu)
+        # TODO: check this
+##        act.setEnabled(editor is not None and editor.selectedText() != '')
+    
+    def __editorOpened(self, editor):
+        """
+        Private slot called, when a new editor was opened.
+        
+        @param editor reference to the new editor (QScintilla.Editor)
+        """
+        menu = editor.getMenu("Tools")
+        if menu is not None:
+            self.__editors[editor] = []
+            if not menu.isEmpty():
+                act = menu.addSeparator()
+                self.__editors[editor].append(act)
+            act = menu.addMenu(self.__menu)
+            self.__editors[editor].append(act)
+##            editor.showMenu.connect(self.__editorShowMenu)
+    
+    def __editorClosed(self, editor):
+        """
+        Private slot called, when an editor was closed.
+        
+        @param editor reference to the editor (QScintilla.Editor)
+        """
+        try:
+            del self.__editors[editor]
+        except KeyError:
+            pass
+    
+##    def __editorShowMenu(self, menuName, menu, editor):
+##        """
+##        Private slot called, when the the editor context menu or a submenu is
+##        about to be shown.
+##        
+##        @param menuName name of the menu to be shown (string)
+##        @param menu reference to the menu (QMenu)
+##        @param editor reference to the editor
+##        """
+##        if menuName == "Tools":
+##            # TODO: check this
+##            self.__menu.setEnabled(editor.selectedText() != '')
+##    
+    def __htm5ToCss3(self):
+        """
+        Private slot handling the HTML5 to CSS3 conversion.
+        """
+        # TODO: implement this
+        from ProjectWeb.Html5ToCss3Converter import Html5ToCss3Converter
+        vm = e5App().getObject("ViewManager")
+        editor = vm.activeWindow()
+        html = editor.selectedText()
+        
+        converter = Html5ToCss3Converter(html)
+        
+        css3 = converter.getCss3()
+        
+        if css3:
+            vm.newEditor()
+            newEditor = vm.activeWindow()
+            newEditor.setText(css3)
+            newEditor.setLanguage("dummy.css")
--- a/PluginWeb.e4p	Tue Dec 30 14:58:24 2014 +0100
+++ b/PluginWeb.e4p	Wed Dec 31 19:14:36 2014 +0100
@@ -15,16 +15,24 @@
   <Eol index="1"/>
   <Sources>
     <Source>__init__.py</Source>
-    <Source>PluginProjectWeb</Source>
+    <Source>ProjectWeb/__init__.py</Source>
+    <Source>PluginProjectWeb.py</Source>
+    <Source>ProjectWeb/Html5ToCss3Converter.py</Source>
+    <Source>ProjectWeb/Html5ToCss3ConverterParameterDialog.py</Source>
   </Sources>
-  <Forms/>
+  <Forms>
+    <Form>ProjectWeb/Html5ToCss3ConverterParameterDialog.ui</Form>
+  </Forms>
   <Translations/>
   <Resources/>
-  <Interfaces/>
+  <Interfaces>
+    <Interface>ProjectWeb/Ui_Html5ToCss3ConverterParameterDialog.py</Interface>
+  </Interfaces>
   <Others>
     <Other>.hgignore</Other>
+    <Other>PluginWeb.e4p</Other>
   </Others>
-  <MainScript>PluginProjectWeb</MainScript>
+  <MainScript>PluginProjectWeb.py</MainScript>
   <Vcs>
     <VcsType>Mercurial</VcsType>
     <VcsOptions>
@@ -141,5 +149,6 @@
     <FiletypeAssociation pattern="*.qrc" type="RESOURCES"/>
     <FiletypeAssociation pattern="*.ts" type="TRANSLATIONS"/>
     <FiletypeAssociation pattern="*.ui" type="FORMS"/>
+    <FiletypeAssociation pattern="Ui_*.py" type="INTERFACES"/>
   </FiletypeAssociations>
 </Project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectWeb/Html5ToCss3Converter.py	Wed Dec 31 19:14:36 2014 +0100
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the HTML5 to CSS3 converter.
+"""
+
+from __future__ import unicode_literals
+
+import os
+import datetime
+import getpass
+import random
+
+from PyQt5.QtCore import QObject
+from PyQt5.QtWidgets import QDialog
+
+from .Html5ToCss3ConverterParameterDialog import \
+    Html5ToCss3ConverterParameterDialog
+
+
+class Html5ToCss3Converter(QObject):
+    """
+    Class implementing the HTML5 to CSS3 converter.
+    """
+    CssTemplate7 = "{0}{1}{2}{3}{4}{5}{6}"
+    CssTemplate8 = "{0}{1}{2}{3}{4}{5}{6}{7}"
+    Placeholders = ('margin:0', 'padding:0', 'border:0', 'font-size:100%',
+                    'font:inherit', 'vertical-align:baseline', 'line-height:1',
+                    'outline:0', 'font-weight:inherit', 'font-style:inherit',
+                    'font-family:inherit', 'vertical-align:baseline')
+    CssToIgnore = ('head', 'meta', 'noscript', 'script', 'style', 'link',
+                   'no-js', 'title', 'object', 'col', 'colgroup', 'option',
+                   'param', 'audio', 'basefont', 'isindex', 'svg', 'area',
+                   'embed', 'br')
+    
+    def __init__(self, html, parent=None):
+        """
+        Constructor
+        
+        @param html HTML text to be converted (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super(Html5ToCss3Converter, self).__init__(parent)
+        
+        self.__html = html
+    
+    def getCss3(self):
+        """
+        Public method to get the converted CSS3 text.
+        
+        @return CSS3 text (string)
+        """
+        dlg = Html5ToCss3ConverterParameterDialog()
+        if dlg.exec_() == QDialog.Accepted:
+            indentation, placeholders = dlg.getData()
+            
+            # TODO: implement this
+            self.__createSoup()
+            
+            alreadyDone = list(self.CssToIgnore)
+            
+            css = '@charset "utf-8";{0}'.format(os.linesep)
+            css += "/* {0} by {1}*/{2}".format(
+                datetime.datetime.now().isoformat().split(".")[0],
+                getpass.getuser(),
+                2 * os.linesep
+            )
+            
+            # step 1: tags
+            for tag in self.__getTags():
+                if tag not in alreadyDone:
+                    css += self.CssTemplate7.format(
+                        tag,
+                        "{",
+                        os.linesep,
+                        indentation,
+                        random.choice(self.Placeholders) + os.linesep
+                            if placeholders else os.linesep,
+                        "}",
+                        os.linesep
+                    )
+                    alreadyDone.append(tag)
+            css += "/*{0}*/{1}".format(
+                "-" * 75,
+                os.linesep
+            )
+            
+            # step 2: IDs
+            for id_ in self.__getIds():
+                if id_ not in alreadyDone:
+                    css += "/* {0} */{1}".format(
+                        "_".join(id_).lower(),
+                        os.linesep
+                    )
+                    css += self.CssTemplate8.format(
+                        "#",
+                        id_[1],
+                        "{",
+                        os.linesep,
+                        indentation,
+                        random.choice(self.Placeholders) + os.linesep
+                            if placeholders else os.linesep,
+                        "}",
+                        os.linesep
+                    )
+                    alreadyDone.append(id_)
+            css += "/*{0}*/{1}".format(
+                "-" * 75,
+                os.linesep
+            )
+            
+            # step 3: classes
+            for class_ in self.__getClasses():
+                if class_ not in alreadyDone:
+                    css += "/* {0} */{1}".format(
+                        "_".join(class_).lower(),
+                        os.linesep
+                    )
+                    css += self.CssTemplate8.format(
+                        ".",
+                        ", .".join(class_[1].split()),
+                        "{",
+                        os.linesep,
+                        indentation,
+                        random.choice(self.Placeholders) + os.linesep
+                            if placeholders else os.linesep,
+                        "}",
+                        os.linesep
+                    )
+                    alreadyDone.append(class_)
+        else:
+            css = ""
+        return css.strip()
+    
+    def __createSoup(self):
+        """
+        Private method to get a BeaitifulSoup object with our HTML text.
+        """
+        from bs4 import BeautifulSoup
+        self.__soup = BeautifulSoup(BeautifulSoup(self.__html).prettify())
+    
+    def __getTags(self):
+        """
+        Private method to extract all tags of the HTML text.
+        
+        @return list of all tags (list of strings)
+        """
+        tags = [t.name for t in self.__soup.find_all(True)]
+        return list(set(tags))
+    
+    def __getClasses(self):
+        """
+        Private method to extract all classes of the HTML text.
+        
+        @return list of tuples containing the tag name and its classes
+            as a blank separated string (list of tuples of two strings)
+        """
+        classes = [(t.name, " ".join(t["class"])) for t in
+                   self.__soup.find_all(True, {"class": True})]
+        return sorted(list(set(classes)))
+    
+    def __getIds(self):
+        """
+        Private method to extract all IDs of the HTML text.
+        
+        @return list of tuples containing the tag name and its ID
+            (list of tuples of two strings)
+        """
+        ids = [(t.name, t["id"]) for t in
+               self.__soup.find_all(True, {"id": True})]
+        return sorted(list(set(ids)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectWeb/Html5ToCss3ConverterParameterDialog.py	Wed Dec 31 19:14:36 2014 +0100
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the CSS conversion parameters.
+"""
+
+from PyQt5.QtWidgets import QDialog
+
+from .Ui_Html5ToCss3ConverterParameterDialog import \
+    Ui_Html5ToCss3ConverterParameterDialog
+
+
+class Html5ToCss3ConverterParameterDialog(
+        QDialog, Ui_Html5ToCss3ConverterParameterDialog):
+    """
+    Class implementing a dialog to enter the CSS conversion parameters.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(Html5ToCss3ConverterParameterDialog, self).__init__(parent)
+        self.setupUi(self)
+    
+    def getData(self):
+        """
+        Public method to get the entered data.
+        
+        @return tuple of indentation string (string) and a flag indicating to
+            use CSS placeholders (boolean)
+        """
+        placeholders = self.placeholderComboBox.currentIndex() == 1
+        return " " * self.indentationSpinBox.value(), placeholders
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectWeb/Html5ToCss3ConverterParameterDialog.ui	Wed Dec 31 19:14:36 2014 +0100
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Html5ToCss3ConverterParameterDialog</class>
+ <widget class="QDialog" name="Html5ToCss3ConverterParameterDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>440</width>
+    <height>102</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Conversion Parameters</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>CSS Indentation Spaces:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QSpinBox" name="indentationSpinBox">
+     <property name="toolTip">
+      <string>Enter the amount of spaces to be used as indentation</string>
+     </property>
+     <property name="minimum">
+      <number>2</number>
+     </property>
+     <property name="maximum">
+      <number>8</number>
+     </property>
+     <property name="singleStep">
+      <number>2</number>
+     </property>
+     <property name="value">
+      <number>2</number>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>220</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>CSS Placeholder:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <widget class="QComboBox" name="placeholderComboBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="toolTip">
+      <string>Select the treatment of empty CSS</string>
+     </property>
+     <item>
+      <property name="text">
+       <string>Blank Empty CSS</string>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Placeholders</string>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="2" column="0" colspan="3">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>Html5ToCss3ConverterParameterDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>Html5ToCss3ConverterParameterDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectWeb/__init__.py	Wed Dec 31 19:14:36 2014 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing web project plug-in data and functions.
+"""

eric ide

mercurial