5 |
5 |
6 """ |
6 """ |
7 Module implementing a previewer widget for HTML, Markdown and ReST files. |
7 Module implementing a previewer widget for HTML, Markdown and ReST files. |
8 """ |
8 """ |
9 |
9 |
10 from __future__ import unicode_literals |
|
11 |
|
12 try: # Only for Py2 |
|
13 import StringIO as io # __IGNORE_EXCEPTION__ |
|
14 str = unicode |
|
15 except (ImportError, NameError): |
|
16 import io # __IGNORE_WARNING__ |
|
17 |
10 |
18 import os |
11 import os |
19 import threading |
12 import threading |
20 import re |
13 import re |
21 import shutil |
14 import shutil |
22 import tempfile |
15 import tempfile |
23 import sys |
16 import sys |
24 |
17 import io |
25 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QSize, QThread |
18 |
|
19 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QThread |
26 from PyQt5.QtGui import QCursor |
20 from PyQt5.QtGui import QCursor |
27 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QCheckBox, \ |
21 from PyQt5.QtWidgets import ( |
28 QSizePolicy, QToolTip |
22 QWidget, QVBoxLayout, QLabel, QCheckBox, QSizePolicy, QToolTip |
|
23 ) |
29 |
24 |
30 from E5Gui.E5Application import e5App |
25 from E5Gui.E5Application import e5App |
31 |
26 |
32 import Utilities |
27 import Utilities |
33 import Preferences |
28 import Preferences |
56 |
51 |
57 try: |
52 try: |
58 from PyQt5.QtWebEngineWidgets import QWebEngineView |
53 from PyQt5.QtWebEngineWidgets import QWebEngineView |
59 self.previewView = QWebEngineView(self) |
54 self.previewView = QWebEngineView(self) |
60 self.previewView.page().linkHovered.connect(self.__showLink) |
55 self.previewView.page().linkHovered.connect(self.__showLink) |
61 self.__usesWebKit = False |
|
62 except ImportError: |
56 except ImportError: |
63 try: |
57 self.__previewAvailable = False |
64 from PyQt5.QtWebKitWidgets import QWebPage, QWebView |
58 self.titleLabel.setText(self.tr( |
65 self.previewView = QWebView(self) |
59 "<b>HTML Preview is not available!<br/>" |
66 self.previewView.page().setLinkDelegationPolicy( |
60 "Install QtWebEngine.</b>")) |
67 QWebPage.DelegateAllLinks) |
61 self.titleLabel.setAlignment(Qt.AlignHCenter) |
68 self.__usesWebKit = True |
62 self.__layout.addStretch() |
69 except ImportError: |
63 return |
70 self.__previewAvailable = False |
|
71 self.titleLabel.setText(self.tr( |
|
72 "<b>HTML Preview is not available!<br/>" |
|
73 "Install QtWebEngine or QtWebKit.</b>")) |
|
74 self.titleLabel.setAlignment(Qt.AlignHCenter) |
|
75 self.__layout.addStretch() |
|
76 return |
|
77 |
64 |
78 sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) |
65 sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) |
79 sizePolicy.setHorizontalStretch(0) |
66 sizePolicy.setHorizontalStretch(0) |
80 sizePolicy.setVerticalStretch(0) |
67 sizePolicy.setVerticalStretch(0) |
81 sizePolicy.setHeightForWidth( |
68 sizePolicy.setHeightForWidth( |
97 self.__layout.addWidget(self.ssiCheckBox) |
84 self.__layout.addWidget(self.ssiCheckBox) |
98 |
85 |
99 self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) |
86 self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) |
100 self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) |
87 self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) |
101 self.previewView.titleChanged.connect(self.on_previewView_titleChanged) |
88 self.previewView.titleChanged.connect(self.on_previewView_titleChanged) |
102 if self.__usesWebKit: |
|
103 self.previewView.linkClicked.connect( |
|
104 self.on_previewView_linkClicked) |
|
105 |
89 |
106 self.jsCheckBox.setChecked( |
90 self.jsCheckBox.setChecked( |
107 Preferences.getUI("ShowFilePreviewJS")) |
91 Preferences.getUI("ShowFilePreviewJS")) |
108 self.ssiCheckBox.setChecked( |
92 self.ssiCheckBox.setChecked( |
109 Preferences.getUI("ShowFilePreviewSSI")) |
93 Preferences.getUI("ShowFilePreviewSSI")) |
187 |
171 |
188 if fn: |
172 if fn: |
189 extension = os.path.normcase(os.path.splitext(fn)[1][1:]) |
173 extension = os.path.normcase(os.path.splitext(fn)[1][1:]) |
190 else: |
174 else: |
191 extension = "" |
175 extension = "" |
192 if extension in \ |
176 if ( |
193 Preferences.getEditor("PreviewHtmlFileNameExtensions") or \ |
177 extension in Preferences.getEditor( |
194 editor.getLanguage() == "HTML": |
178 "PreviewHtmlFileNameExtensions") or |
|
179 editor.getLanguage() == "HTML" |
|
180 ): |
195 language = "HTML" |
181 language = "HTML" |
196 elif extension in \ |
182 elif ( |
197 Preferences.getEditor("PreviewMarkdownFileNameExtensions") or \ |
183 extension in Preferences.getEditor( |
198 editor.getLanguage().lower() == "markdown": |
184 "PreviewMarkdownFileNameExtensions") or |
|
185 editor.getLanguage().lower() == "markdown" |
|
186 ): |
199 language = "Markdown" |
187 language = "Markdown" |
200 elif extension in \ |
188 elif ( |
201 Preferences.getEditor("PreviewRestFileNameExtensions") or \ |
189 extension in Preferences.getEditor( |
202 editor.getLanguage().lower() == "restructuredtext": |
190 "PreviewRestFileNameExtensions") or |
|
191 editor.getLanguage().lower() == "restructuredtext" |
|
192 ): |
203 language = "ReST" |
193 language = "ReST" |
204 else: |
194 else: |
205 self.__setHtml(fn, self.tr( |
195 self.__setHtml(fn, self.tr( |
206 "<p>No preview available for this type of file.</p>")) |
196 "<p>No preview available for this type of file.</p>")) |
207 return |
197 return |
237 @type str |
227 @type str |
238 """ |
228 """ |
239 self.__previewedPath = Utilities.normcasepath( |
229 self.__previewedPath = Utilities.normcasepath( |
240 Utilities.fromNativeSeparators(filePath)) |
230 Utilities.fromNativeSeparators(filePath)) |
241 self.__saveScrollBarPositions() |
231 self.__saveScrollBarPositions() |
242 if self.__usesWebKit: |
232 self.previewView.page().loadFinished.connect( |
243 self.previewView.page().mainFrame().contentsSizeChanged.connect( |
233 self.__restoreScrollBarPositions) |
244 self.__restoreScrollBarPositions) |
234 if not filePath: |
245 else: |
235 filePath = "/" |
246 self.previewView.page().loadFinished.connect( |
|
247 self.__restoreScrollBarPositions) |
|
248 if not filePath: |
|
249 filePath = "/" |
|
250 if rootPath: |
236 if rootPath: |
251 baseUrl = QUrl.fromLocalFile(rootPath + "/index.html") |
237 baseUrl = QUrl.fromLocalFile(rootPath + "/index.html") |
252 else: |
238 else: |
253 baseUrl = QUrl.fromLocalFile(filePath) |
239 baseUrl = QUrl.fromLocalFile(filePath) |
254 self.previewView.setHtml(html, baseUrl=baseUrl) |
240 self.previewView.setHtml(html, baseUrl=baseUrl) |
269 |
255 |
270 def __saveScrollBarPositions(self): |
256 def __saveScrollBarPositions(self): |
271 """ |
257 """ |
272 Private method to save scroll bar positions for a previewed editor. |
258 Private method to save scroll bar positions for a previewed editor. |
273 """ |
259 """ |
274 if self.__usesWebKit: |
260 from PyQt5.QtCore import QPoint |
275 frame = self.previewView.page().mainFrame() |
261 try: |
276 if frame.contentsSize() == QSize(0, 0): |
262 pos = self.previewView.scrollPosition() |
277 return # no valid data, nothing to save |
263 except AttributeError: |
278 |
264 pos = self.__execJavaScript( |
279 pos = frame.scrollPosition() |
265 "(function() {" |
280 self.__scrollBarPositions[self.__previewedPath] = pos |
266 "var res = {" |
281 self.__hScrollBarAtEnd[self.__previewedPath] = \ |
267 " x: 0," |
282 frame.scrollBarMaximum(Qt.Horizontal) == pos.x() |
268 " y: 0," |
283 self.__vScrollBarAtEnd[self.__previewedPath] = \ |
269 "};" |
284 frame.scrollBarMaximum(Qt.Vertical) == pos.y() |
270 "res.x = window.scrollX;" |
285 else: |
271 "res.y = window.scrollY;" |
286 from PyQt5.QtCore import QPoint |
272 "return res;" |
287 try: |
273 "})()" |
288 pos = self.previewView.scrollPosition() |
274 ) |
289 except AttributeError: |
275 if pos is not None: |
290 pos = self.__execJavaScript( |
276 pos = QPoint(pos["x"], pos["y"]) |
291 "(function() {" |
277 else: |
292 "var res = {" |
278 pos = QPoint(0, 0) |
293 " x: 0," |
279 self.__scrollBarPositions[self.__previewedPath] = pos |
294 " y: 0," |
280 self.__hScrollBarAtEnd[self.__previewedPath] = False |
295 "};" |
281 self.__vScrollBarAtEnd[self.__previewedPath] = False |
296 "res.x = window.scrollX;" |
|
297 "res.y = window.scrollY;" |
|
298 "return res;" |
|
299 "})()" |
|
300 ) |
|
301 if pos is not None: |
|
302 pos = QPoint(pos["x"], pos["y"]) |
|
303 else: |
|
304 pos = QPoint(0, 0) |
|
305 self.__scrollBarPositions[self.__previewedPath] = pos |
|
306 self.__hScrollBarAtEnd[self.__previewedPath] = False |
|
307 self.__vScrollBarAtEnd[self.__previewedPath] = False |
|
308 |
282 |
309 def __restoreScrollBarPositions(self): |
283 def __restoreScrollBarPositions(self): |
310 """ |
284 """ |
311 Private method to restore scroll bar positions for a previewed editor. |
285 Private method to restore scroll bar positions for a previewed editor. |
312 """ |
286 """ |
313 if self.__usesWebKit: |
287 if self.__previewedPath not in self.__scrollBarPositions: |
314 try: |
288 return |
315 self.previewView.page().mainFrame().contentsSizeChanged.\ |
289 |
316 disconnect(self.__restoreScrollBarPositions) |
290 pos = self.__scrollBarPositions[self.__previewedPath] |
317 except TypeError: |
291 self.previewView.page().runJavaScript( |
318 # not connected, simply ignore it |
292 "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) |
319 pass |
|
320 |
|
321 if self.__previewedPath not in self.__scrollBarPositions: |
|
322 return |
|
323 |
|
324 frame = self.previewView.page().mainFrame() |
|
325 frame.setScrollPosition( |
|
326 self.__scrollBarPositions[self.__previewedPath]) |
|
327 |
|
328 if self.__hScrollBarAtEnd[self.__previewedPath]: |
|
329 frame.setScrollBarValue( |
|
330 Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) |
|
331 |
|
332 if self.__vScrollBarAtEnd[self.__previewedPath]: |
|
333 frame.setScrollBarValue( |
|
334 Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) |
|
335 else: |
|
336 if self.__previewedPath not in self.__scrollBarPositions: |
|
337 return |
|
338 |
|
339 pos = self.__scrollBarPositions[self.__previewedPath] |
|
340 self.previewView.page().runJavaScript( |
|
341 "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) |
|
342 |
|
343 @pyqtSlot(QUrl) |
|
344 def on_previewView_linkClicked(self, url): |
|
345 """ |
|
346 Private slot handling the clicking of a link. |
|
347 |
|
348 @param url URL of the clicked link |
|
349 @type QUrl |
|
350 """ |
|
351 e5App().getObject("UserInterface").launchHelpViewer(url) |
|
352 |
293 |
353 def __execJavaScript(self, script): |
294 def __execJavaScript(self, script): |
354 """ |
295 """ |
355 Private function to execute a JavaScript function Synchroneously. |
296 Private function to execute a JavaScript function Synchroneously. |
356 |
297 |
715 extensions = ['fenced_code', 'nl2br', 'extra'] |
656 extensions = ['fenced_code', 'nl2br', 'extra'] |
716 else: |
657 else: |
717 extensions = ['fenced_code', 'extra'] |
658 extensions = ['fenced_code', 'extra'] |
718 |
659 |
719 # version 2.0 supports only extension names, not instances |
660 # version 2.0 supports only extension names, not instances |
720 if markdown.version_info[0] > 2 or \ |
661 if ( |
721 (markdown.version_info[0] == 2 and |
662 markdown.version_info[0] > 2 or |
722 markdown.version_info[1] > 0): |
663 (markdown.version_info[0] == 2 and |
|
664 markdown.version_info[1] > 0) |
|
665 ): |
723 class _StrikeThroughExtension(markdown.Extension): |
666 class _StrikeThroughExtension(markdown.Extension): |
724 """ |
667 """ |
725 Class is placed here, because it depends on imported markdown, |
668 Class is placed here, because it depends on imported markdown, |
726 and markdown import is lazy. |
669 and markdown import is lazy. |
727 |
670 |