UI/Previewer.py

changeset 3458
64bbac483843
parent 3190
a9a94491c4fd
child 3459
275cb00c83e2
--- a/UI/Previewer.py	Mon Mar 31 19:13:22 2014 +0200
+++ b/UI/Previewer.py	Mon Mar 31 19:27:31 2014 +0200
@@ -8,24 +8,17 @@
 """
 
 import os
-import threading
-import re
 
-from PyQt4.QtCore import pyqtSlot, pyqtSignal, Qt, QTimer, QSize, QUrl, QThread
-from PyQt4.QtGui import QWidget
-from PyQt4.QtWebKit import QWebPage
-
-from E5Gui.E5Application import e5App
-
-from .Ui_Previewer import Ui_Previewer
+from PyQt4.QtCore import QTimer
+from PyQt4.QtGui import QStackedWidget
 
 import Preferences
-import Utilities
 
 
-class Previewer(QWidget, Ui_Previewer):
+class Previewer(QStackedWidget):
     """
-    Class implementing a previewer widget for HTML, Markdown and ReST files.
+    Class implementing a previewer widget containing a stack of
+    specialized previewers.
     """
     def __init__(self, viewmanager, splitter, parent=None):
         """
@@ -36,29 +29,18 @@
         @param parent reference to the parent widget (QWidget)
         """
         super().__init__(parent)
-        self.setupUi(self)
         
         self.__vm = viewmanager
         self.__splitter = splitter
         
         self.__firstShow = True
         
-        self.previewView.page().setLinkDelegationPolicy(
-            QWebPage.DelegateAllLinks)
+        self.__htmlPreviewer = None
         
         # Don't update too often because the UI might become sluggish
         self.__typingTimer = QTimer()
         self.__typingTimer.setInterval(500)     # 500ms
-        self.__typingTimer.timeout.connect(self.__runProcessingThread)
-        
-        self.__scrollBarPositions = {}
-        self.__vScrollBarAtEnd = {}
-        self.__hScrollBarAtEnd = {}
-        
-        self.__processingThread = PreviewProcessingThread()
-        self.__processingThread.htmlReady.connect(self.__setHtml)
-
-        self.__previewedPath = None
+        self.__typingTimer.timeout.connect(self.__processEditor)
         
         self.__vm.editorChangedEd.connect(self.__editorChanged)
         self.__vm.editorLanguageChanged.connect(self.__editorLanguageChanged)
@@ -78,10 +60,6 @@
         if self.__firstShow:
             self.__splitter.restoreState(
                 Preferences.getUI("PreviewSplitterState"))
-            self.jsCheckBox.setChecked(
-                Preferences.getUI("ShowFilePreviewJS"))
-            self.ssiCheckBox.setChecked(
-                Preferences.getUI("ShowFilePreviewSSI"))
             self.__firstShow = False
         self.__typingTimer.start()
     
@@ -97,7 +75,7 @@
         Public method to perform shutdown actions.
         """
         self.__typingTimer.stop()
-        self.__processingThread.wait()
+        self.__htmlPreviewer and self.__htmlPreviewer.shutdown()
     
     def __splitterMoved(self):
         """
@@ -106,39 +84,6 @@
         state = self.__splitter.saveState()
         Preferences.setUI("PreviewSplitterState", state)
     
-    @pyqtSlot(bool)
-    def on_jsCheckBox_clicked(self, checked):
-        """
-        Private slot to enable/disable JavaScript.
-        
-        @param checked state of the checkbox (boolean)
-        """
-        Preferences.setUI("ShowFilePreviewJS", checked)
-        self.__setJavaScriptEnabled(checked)
-    
-    def __setJavaScriptEnabled(self, enable):
-        """
-        Private method to enable/disable JavaScript.
-        
-        @param enable flag indicating the enable state (boolean)
-        """
-        self.jsCheckBox.setChecked(enable)
-        
-        settings = self.previewView.settings()
-        settings.setAttribute(settings.JavascriptEnabled, enable)
-        
-        self.__runProcessingThread()
-    
-    @pyqtSlot(bool)
-    def on_ssiCheckBox_clicked(self, checked):
-        """
-        Private slot to enable/disable SSI.
-        
-        @param checked state of the checkbox (boolean)
-        """
-        Preferences.setUI("ShowFilePreviewSSI", checked)
-        self.__runProcessingThread()
-    
     def __editorChanged(self, editor):
         """
         Private slot to handle a change of the current editor.
@@ -152,7 +97,7 @@
         if Preferences.getUI("ShowFilePreview") and \
                 self.__isPreviewable(editor):
             self.show()
-            self.__runProcessingThread()
+            self.__processEditor()
         else:
             self.hide()
     
@@ -208,7 +153,7 @@
         
         return False
     
-    def __runProcessingThread(self):
+    def __processEditor(self):
         """
         Private slot to schedule the processing of the current editor's text.
         """
@@ -232,311 +177,19 @@
             elif extension in \
                     Preferences.getEditor("PreviewRestFileNameExtensions"):
                 language = "ReST"
-            else:
-                self.__setHtml(fn, self.tr(
-                    "<p>No preview available for this type of file.</p>"))
-                return
-            
-            if fn:
-                project = e5App().getObject("Project")
-                if project.isProjectFile(fn):
-                    rootPath = project.getProjectPath()
-                else:
-                    rootPath = os.path.dirname(os.path.abspath(fn))
+            elif extension in \
+                    Preferences.getEditor("PreviewQssFileNameExtensions"):
+                language = "QSS"
             else:
-                rootPath = ""
-            
-            self.__processingThread.process(
-                fn, language, editor.text(),
-                self.ssiCheckBox.isChecked(), rootPath)
-
-    def __setHtml(self, filePath, html):
-        """
-        Private method to set the HTML to the view and restore the scroll bars
-        positions.
-        
-        @param filePath file path of the previewed editor (string)
-        @param html processed HTML text ready to be shown (string)
-        """
-        self.__saveScrollBarPositions()
-        self.__previewedPath = Utilities.normcasepath(
-            Utilities.fromNativeSeparators(filePath))
-        self.previewView.page().mainFrame().contentsSizeChanged.connect(
-            self.__restoreScrollBarPositions)
-        self.previewView.setHtml(html, baseUrl=QUrl.fromLocalFile(filePath))
-    
-    @pyqtSlot(str)
-    def on_previewView_titleChanged(self, title):
-        """
-        Private slot to handle a change of the title.
-        
-        @param title new title (string)
-        """
-        if title:
-            self.titleLabel.setText(self.tr("Preview - {0}").format(title))
-        else:
-            self.titleLabel.setText(self.tr("Preview"))
-    
-    def __saveScrollBarPositions(self):
-        """
-        Private method to save scroll bar positions for a previewed editor.
-        """
-        frame = self.previewView.page().mainFrame()
-        if frame.contentsSize() == QSize(0, 0):
-            return  # no valid data, nothing to save
-        
-        pos = frame.scrollPosition()
-        self.__scrollBarPositions[self.__previewedPath] = pos
-        self.__hScrollBarAtEnd[self.__previewedPath] = \
-            frame.scrollBarMaximum(Qt.Horizontal) == pos.x()
-        self.__vScrollBarAtEnd[self.__previewedPath] = \
-            frame.scrollBarMaximum(Qt.Vertical) == pos.y()
-
-    def __restoreScrollBarPositions(self):
-        """
-        Private method to restore scroll bar positions for a previewed editor.
-        """
-        try:
-            self.previewView.page().mainFrame().contentsSizeChanged.disconnect(
-                self.__restoreScrollBarPositions)
-        except TypeError:
-            # not connected, simply ignore it
-            pass
-        
-        if self.__previewedPath not in self.__scrollBarPositions:
-            return
-        
-        frame = self.previewView.page().mainFrame()
-        frame.setScrollPosition(
-            self.__scrollBarPositions[self.__previewedPath])
-        
-        if self.__hScrollBarAtEnd[self.__previewedPath]:
-            frame.setScrollBarValue(
-                Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal))
-        
-        if self.__vScrollBarAtEnd[self.__previewedPath]:
-            frame.setScrollBarValue(
-                Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical))
-    
-    @pyqtSlot(QUrl)
-    def on_previewView_linkClicked(self, url):
-        """
-        Private slot handling the clicking of a link.
-        
-        @param url url of the clicked link (QUrl)
-        """
-        e5App().getObject("UserInterface").launchHelpViewer(url.toString())
-
-
-class PreviewProcessingThread(QThread):
-    """
-    Class implementing a thread to process some text into HTML usable by the
-    previewer view.
-    
-    @signal htmlReady(str,str) emitted with the file name and processed HTML
-        to signal the availability of the processed HTML
-    """
-    htmlReady = pyqtSignal(str, str)
-    
-    def __init__(self, parent=None):
-        """
-        Constructor
-        
-        @param parent reference to the parent object (QObject)
-        """
-        super().__init__()
-        
-        self.__lock = threading.Lock()
-    
-    def process(self, filePath, language, text, ssiEnabled, rootPath):
-        """
-        Convert the given text to HTML.
-        
-        @param filePath file path of the text (string)
-        @param language language of the text (string)
-        @param text text to be processed (string)
-        @param ssiEnabled flag indicating to do some (limited) SSI processing
-            (boolean)
-        @param rootPath root path to be used for SSI processing (str)
-        """
-        with self.__lock:
-            self.__filePath = filePath
-            self.__language = language
-            self.__text = text
-            self.__ssiEnabled = ssiEnabled
-            self.__rootPath = rootPath
-            self.__haveData = True
-            if not self.isRunning():
-                self.start(QThread.LowPriority)
-    
-    def run(self):
-        """
-        Thread function to convert the stored data.
-        """
-        while True:
-            # exits with break
-            with self.__lock:
-                filePath = self.__filePath
-                language = self.__language
-                text = self.__text
-                ssiEnabled = self.__ssiEnabled
-                rootPath = self.__rootPath
-                self.__haveData = False
-            
-            html = self.__getHtml(language, text, ssiEnabled, filePath,
-                                  rootPath)
+                language = ""
             
-            with self.__lock:
-                if not self.__haveData:
-                    self.htmlReady.emit(filePath, html)
-                    break
-                # else - next iteration
-    
-    def __getHtml(self, language, text, ssiEnabled, filePath, rootPath):
-        """
-        Private method to process the given text depending upon the given
-        language.
-        
-        @param language language of the text (string)
-        @param text to be processed (string)
-        @param ssiEnabled flag indicating to do some (limited) SSI processing
-            (boolean)
-        @param filePath file path of the text (string)
-        @param rootPath root path to be used for SSI processing (str)
-        @return processed HTML text (string)
-        """
-        if language == "HTML":
-            if ssiEnabled:
-                return self.__processSSI(text, filePath, rootPath)
-            else:
-                return text
-        elif language == "Markdown":
-            return self.__convertMarkdown(text)
-        elif language == "ReST":
-            return self.__convertReST(text)
-        else:
-            return self.tr(
-                "<p>No preview available for this type of file.</p>")
-    
-    def __processSSI(self, txt, filename, root):
-        """
-        Private method to process the given text for SSI statements.
-        
-        Note: Only a limited subset of SSI statements are supported.
-        
-        @param txt text to be processed (string)
-        @param filename name of the file associated with the given text
-            (string)
-        @param root directory of the document root (string)
-        @return processed HTML (string)
-        """
-        if not filename:
-            return txt
-        
-        # SSI include
-        incRe = re.compile(
-            r"""<!--#include[ \t]+(virtual|file)=[\"']([^\"']+)[\"']\s*-->""",
-            re.IGNORECASE)
-        baseDir = os.path.dirname(os.path.abspath(filename))
-        docRoot = root if root != "" else baseDir
-        while True:
-            incMatch = incRe.search(txt)
-            if incMatch is None:
-                break
-            
-            if incMatch.group(1) == "virtual":
-                incFile = Utilities.normjoinpath(docRoot, incMatch.group(2))
-            elif incMatch.group(1) == "file":
-                incFile = Utilities.normjoinpath(baseDir, incMatch.group(2))
-            else:
-                incFile = ""
-            if os.path.exists(incFile):
-                try:
-                    f = open(incFile, "r")
-                    incTxt = f.read()
-                    f.close()
-                except (IOError, OSError):
-                    # remove SSI include
-                    incTxt = ""
-            else:
-                # remove SSI include
-                incTxt = ""
-            txt = txt[:incMatch.start(0)] + incTxt + txt[incMatch.end(0):]
-        
-        return txt
-    
-    def __convertReST(self, text):
-        """
-        Private method to convert ReST text into HTML.
-        
-        @param text text to be processed (string)
-        @return processed HTML (string)
-        """
-        try:
-            import docutils.core    # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
-        except ImportError:
-            return self.tr(
-                """<p>ReStructuredText preview requires the"""
-                """ <b>python-docutils</b> package.<br/>Install it with"""
-                """ your package manager or see"""
-                """ <a href="http://pypi.python.org/pypi/docutils">"""
-                """this page.</a></p>""")
-        
-        return docutils.core.publish_string(text, writer_name='html')\
-            .decode("utf-8")
-    
-    def __convertMarkdown(self, text):
-        """
-        Private method to convert Markdown text into HTML.
-        
-        @param text text to be processed (string)
-        @return processed HTML (string)
-        """
-        try:
-            import markdown     # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
-        except ImportError:
-            return self.tr(
-                """<p>Markdown preview requires the <b>python-markdown</b> """
-                """package.<br/>Install it with your package manager or see """
-                """<a href="http://pythonhosted.org/Markdown/install.html">"""
-                """installation instructions.</a></p>""")
-        
-        try:
-            import mdx_mathjax  # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
-        except ImportError:
-            #mathjax doesn't require import statement if installed as extension
-            pass
-
-        extensions = ['fenced_code', 'nl2br', 'extra']
-        
-        # version 2.0 supports only extension names, not instances
-        if markdown.version_info[0] > 2 or \
-                (markdown.version_info[0] == 2 and
-                 markdown.version_info[1] > 0):
-            class _StrikeThroughExtension(markdown.Extension):
-                """
-                Class is placed here, because it depends on imported markdown,
-                and markdown import is lazy.
-                
-                (see http://achinghead.com/
-                python-markdown-adding-insert-delete.html this page for
-                details)
-                """
-                DEL_RE = r'(~~)(.*?)~~'
-
-                def extendMarkdown(self, md, md_globals):
-                    # Create the del pattern
-                    del_tag = markdown.inlinepatterns.SimpleTagPattern(
-                        self.DEL_RE, 'del')
-                    # Insert del pattern into markdown parser
-                    md.inlinePatterns.add('del', del_tag, '>not_strong')
-            
-            extensions.append(_StrikeThroughExtension())
-
-        try:
-            return markdown.markdown(text,  extensions + ['mathjax'])
-        except (ImportError, ValueError):
-            # markdown raises ValueError or ImportError, depends on version
-            # It is not clear, how to distinguish missing mathjax from other
-            # errors. So keep going without mathjax.
-            return markdown.markdown(text, extensions)
+            if language in ["HTML", "Markdown", "ReST"]:
+                if self.__htmlPreviewer is None:
+                    from .Previewers.PreviewerHTML import PreviewerHTML
+                    self.__htmlPreviewer = PreviewerHTML()
+                    self.addWidget(self.__htmlPreviewer)
+                self.setCurrentWidget(self.__htmlPreviewer)
+                self.__htmlPreviewer.processEditor(editor)
+            elif language == "QSS":
+                # TODO: add QSS
+                pass

eric ide

mercurial