41 self.__vm = viewmanager |
41 self.__vm = viewmanager |
42 self.__splitter = splitter |
42 self.__splitter = splitter |
43 |
43 |
44 self.__firstShow = True |
44 self.__firstShow = True |
45 |
45 |
46 self.previewView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) |
46 self.previewView.page().setLinkDelegationPolicy( |
|
47 QWebPage.DelegateAllLinks) |
47 |
48 |
48 # Don't update too often because the UI might become sluggish |
49 # Don't update too often because the UI might become sluggish |
49 self.__typingTimer = QTimer() |
50 self.__typingTimer = QTimer() |
50 self.__typingTimer.setInterval(500) # 500ms |
51 self.__typingTimer.setInterval(500) # 500ms |
51 self.__typingTimer.timeout.connect(self.__runProcessingThread) |
52 self.__typingTimer.timeout.connect(self.__runProcessingThread) |
73 """ |
74 """ |
74 Public method to show the preview widget. |
75 Public method to show the preview widget. |
75 """ |
76 """ |
76 super().show() |
77 super().show() |
77 if self.__firstShow: |
78 if self.__firstShow: |
78 self.__splitter.restoreState(Preferences.getUI("PreviewSplitterState")) |
79 self.__splitter.restoreState( |
79 self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS")) |
80 Preferences.getUI("PreviewSplitterState")) |
80 self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI")) |
81 self.jsCheckBox.setChecked( |
|
82 Preferences.getUI("ShowFilePreviewJS")) |
|
83 self.ssiCheckBox.setChecked( |
|
84 Preferences.getUI("ShowFilePreviewSSI")) |
81 self.__firstShow = False |
85 self.__firstShow = False |
82 self.__typingTimer.start() |
86 self.__typingTimer.start() |
83 |
87 |
84 def hide(self): |
88 def hide(self): |
85 """ |
89 """ |
181 else: |
186 else: |
182 self.hide() |
187 self.hide() |
183 |
188 |
184 def __isPreviewable(self, editor): |
189 def __isPreviewable(self, editor): |
185 """ |
190 """ |
186 Private method to check, if a preview can be shown for the given editor. |
191 Private method to check, if a preview can be shown for the given |
|
192 editor. |
187 |
193 |
188 @param editor reference to an editor (Editor) |
194 @param editor reference to an editor (Editor) |
189 @return flag indicating if a preview can be shown (boolean) |
195 @return flag indicating if a preview can be shown (boolean) |
190 """ |
196 """ |
191 if editor: |
197 if editor: |
192 if editor.getFileName() is not None: |
198 if editor.getFileName() is not None: |
193 extension = os.path.normcase( |
199 extension = os.path.normcase( |
194 os.path.splitext(editor.getFileName())[1][1:]) |
200 os.path.splitext(editor.getFileName())[1][1:]) |
195 return extension in \ |
201 return extension in \ |
196 Preferences.getEditor("PreviewHtmlFileNameExtensions") + \ |
202 Preferences.getEditor("PreviewHtmlFileNameExtensions") + \ |
197 Preferences.getEditor("PreviewMarkdownFileNameExtensions") + \ |
203 Preferences.getEditor( |
|
204 "PreviewMarkdownFileNameExtensions") + \ |
198 Preferences.getEditor("PreviewRestFileNameExtensions") |
205 Preferences.getEditor("PreviewRestFileNameExtensions") |
199 elif editor.getLanguage() == "HTML": |
206 elif editor.getLanguage() == "HTML": |
200 return True |
207 return True |
201 |
208 |
202 return False |
209 return False |
243 fn, language, editor.text(), |
250 fn, language, editor.text(), |
244 self.ssiCheckBox.isChecked(), rootPath) |
251 self.ssiCheckBox.isChecked(), rootPath) |
245 |
252 |
246 def __setHtml(self, filePath, html): |
253 def __setHtml(self, filePath, html): |
247 """ |
254 """ |
248 Private method to set the HTML to the view and restore the scroll bars positions. |
255 Private method to set the HTML to the view and restore the scroll bars |
|
256 positions. |
249 |
257 |
250 @param filePath file path of the previewed editor (string) |
258 @param filePath file path of the previewed editor (string) |
251 @param html processed HTML text ready to be shown (string) |
259 @param html processed HTML text ready to be shown (string) |
252 """ |
260 """ |
253 self.__saveScrollBarPositions() |
261 self.__saveScrollBarPositions() |
297 |
305 |
298 if self.__previewedPath not in self.__scrollBarPositions: |
306 if self.__previewedPath not in self.__scrollBarPositions: |
299 return |
307 return |
300 |
308 |
301 frame = self.previewView.page().mainFrame() |
309 frame = self.previewView.page().mainFrame() |
302 frame.setScrollPosition(self.__scrollBarPositions[self.__previewedPath]) |
310 frame.setScrollPosition( |
|
311 self.__scrollBarPositions[self.__previewedPath]) |
303 |
312 |
304 if self.__hScrollBarAtEnd[self.__previewedPath]: |
313 if self.__hScrollBarAtEnd[self.__previewedPath]: |
305 frame.setScrollBarValue(Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) |
314 frame.setScrollBarValue( |
|
315 Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) |
306 |
316 |
307 if self.__vScrollBarAtEnd[self.__previewedPath]: |
317 if self.__vScrollBarAtEnd[self.__previewedPath]: |
308 frame.setScrollBarValue(Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) |
318 frame.setScrollBarValue( |
|
319 Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) |
309 |
320 |
310 @pyqtSlot(QUrl) |
321 @pyqtSlot(QUrl) |
311 def on_previewView_linkClicked(self, url): |
322 def on_previewView_linkClicked(self, url): |
312 """ |
323 """ |
313 Private slot handling the clicking of a link. |
324 Private slot handling the clicking of a link. |
320 class PreviewProcessingThread(QThread): |
331 class PreviewProcessingThread(QThread): |
321 """ |
332 """ |
322 Class implementing a thread to process some text into HTML usable by the |
333 Class implementing a thread to process some text into HTML usable by the |
323 previewer view. |
334 previewer view. |
324 |
335 |
325 @signal htmlReady(str,str) emitted with the file name and processed HTML to signal |
336 @signal htmlReady(str,str) emitted with the file name and processed HTML |
326 the availability of the processed HTML |
337 to signal the availability of the processed HTML |
327 """ |
338 """ |
328 htmlReady = pyqtSignal(str, str) |
339 htmlReady = pyqtSignal(str, str) |
329 |
340 |
330 def __init__(self, parent=None): |
341 def __init__(self, parent=None): |
331 """ |
342 """ |
342 Convert the given text to HTML. |
353 Convert the given text to HTML. |
343 |
354 |
344 @param filePath file path of the text (string) |
355 @param filePath file path of the text (string) |
345 @param language language of the text (string) |
356 @param language language of the text (string) |
346 @param text text to be processed (string) |
357 @param text text to be processed (string) |
347 @param ssiEnabled flag indicating to do some (limited) SSI processing (boolean) |
358 @param ssiEnabled flag indicating to do some (limited) SSI processing |
|
359 (boolean) |
348 @param rootPath root path to be used for SSI processing (str) |
360 @param rootPath root path to be used for SSI processing (str) |
349 """ |
361 """ |
350 with self.__lock: |
362 with self.__lock: |
351 self.__filePath = filePath |
363 self.__filePath = filePath |
352 self.__language = language |
364 self.__language = language |
369 text = self.__text |
381 text = self.__text |
370 ssiEnabled = self.__ssiEnabled |
382 ssiEnabled = self.__ssiEnabled |
371 rootPath = self.__rootPath |
383 rootPath = self.__rootPath |
372 self.__haveData = False |
384 self.__haveData = False |
373 |
385 |
374 html = self.__getHtml(language, text, ssiEnabled, filePath, rootPath) |
386 html = self.__getHtml(language, text, ssiEnabled, filePath, |
|
387 rootPath) |
375 |
388 |
376 with self.__lock: |
389 with self.__lock: |
377 if not self.__haveData: |
390 if not self.__haveData: |
378 self.htmlReady.emit(filePath, html) |
391 self.htmlReady.emit(filePath, html) |
379 break |
392 break |
400 elif language == "Markdown": |
413 elif language == "Markdown": |
401 return self.__convertMarkdown(text) |
414 return self.__convertMarkdown(text) |
402 elif language == "ReST": |
415 elif language == "ReST": |
403 return self.__convertReST(text) |
416 return self.__convertReST(text) |
404 else: |
417 else: |
405 return self.trUtf8("<p>No preview available for this type of file.</p>") |
418 return self.trUtf8( |
|
419 "<p>No preview available for this type of file.</p>") |
406 |
420 |
407 def __processSSI(self, txt, filename, root): |
421 def __processSSI(self, txt, filename, root): |
408 """ |
422 """ |
409 Private method to process the given text for SSI statements. |
423 Private method to process the given text for SSI statements. |
410 |
424 |
411 Note: Only a limited subset of SSI statements are supported. |
425 Note: Only a limited subset of SSI statements are supported. |
412 |
426 |
413 @param txt text to be processed (string) |
427 @param txt text to be processed (string) |
414 @param filename name of the file associated with the given text (string) |
428 @param filename name of the file associated with the given text |
|
429 (string) |
415 @param root directory of the document root (string) |
430 @param root directory of the document root (string) |
416 @return processed HTML (string) |
431 @return processed HTML (string) |
417 """ |
432 """ |
418 if not filename: |
433 if not filename: |
419 return txt |
434 return txt |
459 """ |
474 """ |
460 try: |
475 try: |
461 import docutils.core # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
476 import docutils.core # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
462 except ImportError: |
477 except ImportError: |
463 return self.trUtf8( |
478 return self.trUtf8( |
464 """<p>ReStructuredText preview requires the <b>python-docutils</b> """ |
479 """<p>ReStructuredText preview requires the""" |
465 """package.<br/>Install it with your package manager or see """ |
480 """ <b>python-docutils</b> package.<br/>Install it with""" |
466 """<a href="http://pypi.python.org/pypi/docutils">this page.</a></p>""") |
481 """ your package manager or see""" |
467 |
482 """ <a href="http://pypi.python.org/pypi/docutils">""" |
468 return docutils.core.publish_string(text, writer_name='html').decode("utf-8") |
483 """this page.</a></p>""") |
|
484 |
|
485 return docutils.core.publish_string(text, writer_name='html')\ |
|
486 .decode("utf-8") |
469 |
487 |
470 def __convertMarkdown(self, text): |
488 def __convertMarkdown(self, text): |
471 """ |
489 """ |
472 Private method to convert Markdown text into HTML. |
490 Private method to convert Markdown text into HTML. |
473 |
491 |
491 |
509 |
492 extensions = ['fenced_code', 'nl2br', 'extra'] |
510 extensions = ['fenced_code', 'nl2br', 'extra'] |
493 |
511 |
494 # version 2.0 supports only extension names, not instances |
512 # version 2.0 supports only extension names, not instances |
495 if markdown.version_info[0] > 2 or \ |
513 if markdown.version_info[0] > 2 or \ |
496 (markdown.version_info[0] == 2 and markdown.version_info[1] > 0): |
514 (markdown.version_info[0] == 2 and |
497 |
515 markdown.version_info[1] > 0): |
498 class _StrikeThroughExtension(markdown.Extension): |
516 class _StrikeThroughExtension(markdown.Extension): |
499 """ |
517 """ |
500 Class is placed here, because it depends on imported markdown, |
518 Class is placed here, because it depends on imported markdown, |
501 and markdown import is lazy. |
519 and markdown import is lazy. |
502 |
520 |
503 (see |
521 (see http://achinghead.com/ |
504 <a href="http://achinghead.com/python-markdown-adding-insert-delete.html"> |
522 python-markdown-adding-insert-delete.html this page for |
505 this page for details</a>) |
523 details) |
506 """ |
524 """ |
507 DEL_RE = r'(~~)(.*?)~~' |
525 DEL_RE = r'(~~)(.*?)~~' |
508 |
526 |
509 def extendMarkdown(self, md, md_globals): |
527 def extendMarkdown(self, md, md_globals): |
510 # Create the del pattern |
528 # Create the del pattern |
511 del_tag = markdown.inlinepatterns.SimpleTagPattern(self.DEL_RE, 'del') |
529 del_tag = markdown.inlinepatterns.SimpleTagPattern( |
|
530 self.DEL_RE, 'del') |
512 # Insert del pattern into markdown parser |
531 # Insert del pattern into markdown parser |
513 md.inlinePatterns.add('del', del_tag, '>not_strong') |
532 md.inlinePatterns.add('del', del_tag, '>not_strong') |
514 |
533 |
515 extensions.append(_StrikeThroughExtension()) |
534 extensions.append(_StrikeThroughExtension()) |
516 |
535 |
517 try: |
536 try: |
518 return markdown.markdown(text, extensions + ['mathjax']) |
537 return markdown.markdown(text, extensions + ['mathjax']) |
519 except (ImportError, ValueError): |
538 except (ImportError, ValueError): |
520 # markdown raises ValueError or ImportError, depends on version |
539 # markdown raises ValueError or ImportError, depends on version |
521 # It is not clear, how to distinguish missing mathjax from other errors. |
540 # It is not clear, how to distinguish missing mathjax from other |
522 # So keep going without mathjax. |
541 # errors. So keep going without mathjax. |
523 return markdown.markdown(text, extensions) |
542 return markdown.markdown(text, extensions) |