Wed, 31 Dec 2014 19:14:36 +0100
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 |
diff -r 9099ee404178 -r e3a92a671aa5 .hgignore --- 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
diff -r 9099ee404178 -r e3a92a671aa5 PluginProjectWeb.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")
diff -r 9099ee404178 -r e3a92a671aa5 PluginWeb.e4p --- 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>
diff -r 9099ee404178 -r e3a92a671aa5 ProjectWeb/Html5ToCss3Converter.py --- /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)))
diff -r 9099ee404178 -r e3a92a671aa5 ProjectWeb/Html5ToCss3ConverterParameterDialog.py --- /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
diff -r 9099ee404178 -r e3a92a671aa5 ProjectWeb/Html5ToCss3ConverterParameterDialog.ui --- /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>
diff -r 9099ee404178 -r e3a92a671aa5 ProjectWeb/__init__.py --- /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. +"""